<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ClayBrainer]]></title><description><![CDATA[ClayBrainer is a cloud-native tech blog covering Kubernetes, DevOps, Cloud Architecture, System Design, and production-grade engineering insights.]]></description><link>https://claybrainer.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 11 Sep 2026 05:03:32 GMT</lastBuildDate><atom:link href="https://claybrainer.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AI - Fundamentals : Part 2 : Context Window and Attention Horizon]]></title><description><![CDATA[We are covering the fundamentals of AI so that we can build a strong foundation before moving on to more advanced AI topics. In the previous blog, we explored one of the most important concepts in AI ]]></description><link>https://claybrainer.com/ai-fundamentals-part-2-context-window-and-attention-horizon</link><guid isPermaLink="true">https://claybrainer.com/ai-fundamentals-part-2-context-window-and-attention-horizon</guid><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[context-window]]></category><category><![CDATA[attention-horizon]]></category><category><![CDATA[AI]]></category><category><![CDATA[LLM Context Window]]></category><category><![CDATA[contextwindow]]></category><category><![CDATA[long context window]]></category><category><![CDATA[AI Context Window Comparison]]></category><category><![CDATA[context windows]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 12 Jul 2026 08:28:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/71752660-b260-4c76-a46f-ce2342ad10fc.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We are covering the fundamentals of AI so that we can build a strong foundation before moving on to more advanced AI topics. In the previous blog, we explored one of the most important concepts in AI <strong>Tokens</strong>. We discussed what tokens are, the difference between <strong>input tokens</strong> and <strong>output tokens</strong>, how tokens are calculated, and why writing efficient prompts can help reduce token usage while getting better results.</p>
<p>If you're just joining this series, I highly recommend reading the <a href="https://claybrainer.com/ai-fundamentals-part-1-llm-tokens">previous blog first</a>, as the concepts we'll discuss here build directly on top of it.</p>
<p>With that said, let's move on to the next building blocks of AI <strong>Context Window</strong> and <strong>Attention Horizon</strong>. These two concepts determine how much information an AI model can remember during a conversation and how effectively it can use that information to generate meaningful responses.</p>
<p>Let's dive in.</p>
<h1>Context Window</h1>
<p>Now that we know <strong>how data enters an AI model in the form of tokens let's explore where those tokens live while the model is processing your request.</strong></p>
<p>Think of the <strong>Context Window</strong> as the model's working memory. It contains everything the model can "see" at a given moment before generating a response.</p>
<p>Every AI model has a <strong>hard limit</strong> on the number of tokens it can process in a single interaction. This limit is called the <strong>Context Window</strong>.</p>
<p>For example:</p>
<ul>
<li><p>Older models could process around <strong>8,000 tokens</strong>.</p>
</li>
<li><p>Modern frontier models can process <strong>128K, 200K, or even over 1 million tokens</strong> in a single context window.</p>
</li>
</ul>
<p>When you type a prompt, it is first split into <strong>tokens</strong>. These tokens are then assigned <strong>token IDs</strong> and converted into <strong>numerical vectors</strong> that the AI model can understand and process. In simple terms, <strong>tokens are just small pieces of text that are transformed into numbers</strong> before the model begins generating a response.</p>
<p>Almost every AI model specifies its <strong>context window</strong> in its official documentation. For example, here's the documentation for <a href="https://ollama.com/library/gemma4"><strong>Gemma</strong></a>, where you can see its supported context window:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/092d5852-ef0d-4aef-9cce-bd1949b8c3ca.png" alt="" style="display:block;margin:0 auto" />

<h2>Why the Context Window Matters</h2>
<p>The quality of your input has a direct impact on the quality of the model's output.</p>
<p>A common misconception is that the <strong>Context Window</strong> refers only to the input prompt. It doesn't.</p>
<p>The context window includes:</p>
<ul>
<li><p>Your input tokens</p>
</li>
<li><p>Previous conversation (if applicable)</p>
</li>
<li><p>The model's generated output</p>
</li>
</ul>
<p>For example, if a model supports a <strong>128K token context window</strong> and generates a <strong>4K token response</strong>, then approximately <strong>124K tokens</strong> remain available for your input and conversation history.</p>
<p>This is why context management becomes increasingly important when building AI applications.</p>
<p>Now that you understand what a <strong>context window</strong> is, the next question you're probably asking is:</p>
<h2>Does a Larger Context Window Mean Better Memory?</h2>
<p>Not necessarily. Just because a model can accept a massive log file or an entire codebase doesn't mean it understands or remembers every part of it equally well.</p>
<p>This brings us to an interesting phenomenon known as <strong>Lost in the Middle</strong>.</p>
<h3>Lost in the Middle</h3>
<p>Research has shown that many Large Language Models exhibit a behavior called <strong>Lost in the Middle</strong>.</p>
<p>They are generally very good at remembering information placed:</p>
<ul>
<li><p>At the <strong>beginning</strong> of a prompt</p>
</li>
<li><p>At the <strong>end</strong> of a prompt</p>
</li>
</ul>
<p>However, information buried somewhere in the <strong>middle</strong> of a very long prompt is more likely to receive less attention, making it easier for the model to overlook.</p>
<p>Let's take a real world example.</p>
<p>Imagine one of your production workflows fails. Instead of extracting only the relevant error logs, you send the <strong>entire log file</strong> to the LLM. The log contains <strong>tens of thousands of lines</strong>, and the actual error occurred somewhere in the middle.</p>
<p>There's a good chance the model may miss the most important error or fail to connect it with the surrounding events. As a result, it might provide an incomplete diagnosis or even suggest the wrong root cause.</p>
<p>This behavior is commonly referred to as <strong>Lost in the Middle</strong>.</p>
<blockquote>
<p><strong>Note:</strong> This doesn't happen every time, and newer models are continuously improving. However, it is still a well-observed limitation when working with very large contexts.</p>
</blockquote>
<h2>How the Context Window Impacts Performance</h2>
<p>When working with large inputs, there are two important limits to understand.</p>
<h3>Context Window (Input + Output Limit)</h3>
<p>This is the total number of tokens the model can process during a single interaction, <strong>including both the prompt and the generated response</strong>.</p>
<h3>Maximum Output Tokens</h3>
<p>This is the maximum number of tokens the model is allowed to generate in its response.</p>
<p>For example, a model might accept 128,000 tokens of context but be configured to generate only 4,000 output tokens. These are two different limits and are often confused.</p>
<p>If you exceed the available context window, the model cannot process the extra information. Depending on the model, it may reject the request, truncate older content, or lose important context. Even before reaching the hard limit, extremely large prompts can reduce response quality because of the Lost in the Middle effect.</p>
<p>Interestingly, you don't always need to reach the hard limit to notice a drop in quality. With very large prompts, models can suffer from the <strong>"Lost in the Middle"</strong> effect, where information placed in the middle of a long context receives less attention than content at the beginning or the end. This can lead to missed details or less accurate responses.</p>
<p>Have you ever been in a long conversation with an AI where everything starts off perfectly, but after dozens of prompts it begins forgetting details, asks questions you've already answered, or even stops accepting more input? That's usually because the conversation is approaching the model's <strong>context window</strong> limit.</p>
<p>Every prompt you send and every response the AI generates consumes part of this limited space. Once it's full, the application must either remove older messages, replace them with a summary, or reject additional input altogether. So, the AI isn't getting "confused" it's simply running out of room to keep the entire conversation in memory.</p>
<h2>So, How Do We Avoid Lost in the Middle?</h2>
<p>Fortunately, there are several techniques that significantly improve both accuracy and cost.</p>
<h3>Trimming and Slicing</h3>
<p>Instead of sending a raw 50,000 line log file, extract only the relevant sections.</p>
<p>For example, search for keywords such as: <code>ERROR</code> <code>CRITICAL</code> <code>FAIL</code> <code>EXCEPTION</code></p>
<p>Then include a few lines before and after the matching entries to preserve context.</p>
<p>This gives the model exactly what it needs without overwhelming it.</p>
<h3>Place Important Information Strategically</h3>
<p>If you must include a large amount of reference data, place your most important instructions or questions at the beginning or end of the prompt rather than burying them in the middle.</p>
<p>This increases the likelihood that the model will pay attention to the information that matters most.</p>
<h3>Use Retrieval Instead of Sending Everything</h3>
<p>In production AI applications, it's usually better to retrieve only the relevant documents, log snippets, or code sections before sending them to the LLM.</p>
<p>This approach reduces token usage, lowers cost, improves accuracy, and minimizes the chances of the model getting "lost in the middle."</p>
<p>There are many more advanced techniques used to manage context efficiently, which we'll explore in upcoming blogs.</p>
<h1>Attention Horizon</h1>
<p>At this point, you might be wondering:</p>
<blockquote>
<p>If my file is only <strong>100K tokens</strong> and the model supports a <strong>128K token context window</strong>, shouldn't it be able to read the entire file? If so, why does it sometimes miss information that's right in the middle?</p>
</blockquote>
<p>The answer lies in something called the <strong>Attention Horizon</strong>.</p>
<p>While the <strong>Context Window</strong> defines <strong>how much information the model can see</strong>, the <strong>Attention Horizon</strong> describes <strong>how effectively the model can focus on different parts of that information</strong></p>
<p>Think of it this way:</p>
<ul>
<li><p><strong>Context Window = Everything the model can read.</strong></p>
</li>
<li><p><strong>Attention Horizon = What the model can effectively focus on while generating the next token.</strong></p>
</li>
</ul>
<p>Imagine someone pours <strong>10,000 balls</strong> onto a football field. Only <strong>30 of them are red</strong>, and the remaining <strong>9,970 are white and blue.</strong> The red balls are there you haven't lost them but spotting all 30 quickly is difficult because they're scattered among thousands of others. Now imagine there are only <strong>100 balls</strong> with the same <strong>30 red balls</strong>. Suddenly, finding the red ones becomes much easier because there are far fewer distractions.</p>
<p>Large Language Models work in a similar way. Even if all the information fits inside the context window, the model has to distribute its attention across thousands of tokens. As the context becomes larger, important pieces of information especially those buried in the middle have to compete with everything else for attention. They aren't missing; they're simply harder for the model to focus on. This is why models can overlook details that are technically within the context window, this is one of the reasons why models sometimes exhibit the <strong>Lost in the Middle</strong> behavior.</p>
<p><strong>Attention Horizon</strong> is <strong>not an official metric</strong> like the <strong>Context Window</strong>. Instead, it's a conceptual way of describing how effectively a model can focus on and make use of information distributed across its available context.</p>
<p>Since it's a conceptual term rather than a published specification, you won't find it listed in a model's documentation so don't bother searching for it. 🙂</p>
<h3>A Real-World Example</h3>
<p>Suppose you provide an LLM with:</p>
<ul>
<li><p>40,000 lines of application logs</p>
</li>
<li><p>Kubernetes manifests</p>
</li>
<li><p>Terraform code</p>
</li>
<li><p>CI/CD pipeline output</p>
</li>
<li><p>Monitoring alerts</p>
</li>
</ul>
<p>In this file, the actual root cause is hidden around <strong>line 22,000</strong>: <code>java.lang.OutOfMemoryError: Java heap space</code></p>
<p>Although the model can technically read all of this information, its attention is spread across thousands of tokens. The important error may not receive enough focus, causing the model to miss it or provide a less accurate diagnosis.</p>
<p>Now imagine instead that you extract only:</p>
<ul>
<li><p>The error message</p>
</li>
<li><p>20 lines before it</p>
</li>
<li><p>20 lines after it</p>
</li>
</ul>
<p>Now, almost every token in the context is relevant. The model can focus its attention much more effectively, resulting in a faster, cheaper, and more accurate response.</p>
<p>This is why <strong>a larger Context Window doesn't automatically guarantee better performance</strong>. The model still needs to decide <strong>where to focus its attention</strong>, and that's where the concept of <strong>Attention Horizon</strong> becomes important.</p>
<h1>Summary</h1>
<p>Let's quickly recap what we've learned:</p>
<ul>
<li><p><strong>Context Window</strong> is the maximum amount of information (measured in tokens) that a model can process in a single interaction. It includes both your input and the model's generated output.</p>
</li>
<li><p>A <strong>larger context window</strong> allows you to provide more information, but it <strong>doesn't guarantee</strong> that the model will understand or utilize every part of it equally well.</p>
</li>
<li><p>Models can exhibit a behavior known as <strong>Lost in the Middle</strong>, where important information buried deep within a long prompt may receive less attention than information placed at the beginning or the end.</p>
</li>
<li><p><strong>Attention Horizon</strong> is a conceptual way to think about how effectively a model can focus on different parts of the context. While it's not an official model specification, it helps explain why simply increasing the context window doesn't always improve response quality.</p>
</li>
<li><p>To get better results, send <strong>only the most relevant information</strong>. Trim unnecessary content, place critical instructions strategically, and retrieve only the sections the model actually needs instead of overwhelming it with massive inputs.</p>
</li>
</ul>
<p>The key takeaway is simple:</p>
<blockquote>
<p><strong>A bigger Context Window lets the model see more, but a well-structured prompt helps the model understand more.</strong></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[AI - Fundamentals : Part 1 : LLM Tokens]]></title><description><![CDATA[There has been a lot of buzz around AI recently. Everywhere you look, people are talking about it, building with it, or trying to figure out how to use it. And, like many of you, we all want to jump o]]></description><link>https://claybrainer.com/ai-fundamentals-part-1-llm-tokens</link><guid isPermaLink="true">https://claybrainer.com/ai-fundamentals-part-1-llm-tokens</guid><category><![CDATA[tokens]]></category><category><![CDATA[tokens in ai]]></category><category><![CDATA[llm-tokens]]></category><category><![CDATA[LLM Tokenization Strategies]]></category><category><![CDATA[llm token governance]]></category><category><![CDATA[AI]]></category><category><![CDATA[aitools]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 05 Jul 2026 04:37:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/04500e74-4ffc-476c-93e2-1e260c19d514.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There has been a lot of buzz around AI recently. Everywhere you look, people are talking about it, building with it, or trying to figure out how to use it. And, like many of you, we all want to jump on the AI train. 🚂</p>
<p>But before we start building AI-powered applications or experimenting with the latest models, it's important to understand the core mechanics. A strong foundation makes learning advanced concepts much easier and helps you understand <em>why</em> things work instead of just <em>how</em> to use them.</p>
<p>That’s why, <strong>in this multi part series, we are going to cover all the essential AI fundamentals</strong> step by step, giving you a solid launchpad for your engineering journey.</p>
<p>Let’s kick things off with the absolute baseline of how language models process information. <strong>Part 1: Tokens.</strong></p>
<h1>Token</h1>
<p>Language models do not read whole words like humans do, nor do they look at individual characters one by one. Instead, they process text in chunks called <strong>tokens</strong>. A token can be an entire word, a part of a word (like a syllable), or even a single punctuation mark or space.</p>
<p>Think of it like LEGO bricks. Instead of manufacturing a unique brick for every single object in the world (words), or forcing you to build everything out of microscopic 1x1 pegs (characters), we use a standard set of pre made bricks of various sizes (tokens) to build everything efficiently.</p>
<h2>Byte Pair Encoding</h2>
<p>Modern models use an algorithm called <strong>Byte-Pair Encoding (BPE)</strong> to build their vocabulary of tokens. Here is the simple intuition of how BPE works from scratch:</p>
<ul>
<li><p>It starts by treating every individual character as a token.</p>
</li>
<li><p>It looks at a massive dataset of text and finds the most frequently occurring pair of characters side-by-side (e.g., "t" and "h").</p>
</li>
<li><p>It merges that frequent pair into a new, single token: "th".</p>
</li>
<li><p>It repeats this process millions of times, constantly merging the most common adjacent pieces into larger tokens (like "the", or "ing").</p>
</li>
</ul>
<p>Because of this, common words like <code>the</code> or <code>infrastructure</code> usually become a single token. Rare words, typos, or complex code blocks get broken down into multiple smaller tokens.</p>
<h2>🪙 The Golden Rule of AI Cost</h2>
<p>As an engineer, this is where the architecture meets the budget: <strong>Models bill you per token, not per word or character.</strong></p>
<p>Generally, <strong>1 token is roughly equal to 0.75 words</strong> in English. However, unexpected things can inflate your token count and therefore your cloud bill.</p>
<p>To see this in action, let's look at how spaces and punctuation change things. Consider these two inputs:</p>
<ul>
<li><p><strong>Input A:</strong> <code>Identify the error.</code></p>
</li>
<li><p><strong>Input B:</strong> <code>Identify the error .</code> (with extra spaces before and after the period)</p>
</li>
</ul>
<p>In Input A, the punctuation might be cleanly bundled. In Input B, the tokenizer has to create distinct, individual tokens for each unexpected space and isolated punctuation mark because that specific sequence isn't a common "merged brick" in its vocabulary.</p>
<h2>💳 The Token-to-Cost Ratio</h2>
<p>When building production infrastructure, token counts translate directly to operational costs. LLM providers bill you based on two distinct metrics:</p>
<ol>
<li><p><strong>Input Tokens (Prompt):</strong> The text you send to the model.</p>
</li>
<li><p><strong>Output Tokens (Completion):</strong> The text the model generates back to you.</p>
</li>
</ol>
<p>Output tokens are almost always more expensive than input tokens because the model has to expend more compute power generating words one by one.</p>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Model Type</strong></p></td><td><p><strong>Input Cost (per 1M tokens)</strong></p></td><td><p><strong>Output Cost (per 1M tokens)</strong></p></td></tr><tr><td><p><strong>Lightweight / Fast</strong> (e.g., GPT-4o-mini)</p></td><td><p>$0.15</p></td><td><p>$0.60</p></td></tr><tr><td><p><strong>Premium / Powerful</strong> (e.g., GPT-4o)</p></td><td><p>$2.50</p></td><td><p>$10.00</p></td></tr></tbody></table>

<h2>🔍 How Spaces and Indentations Multiply Costs</h2>
<p>In standard code formatting, engineers use spaces or tabs for readability. However, many tokenizers process individual spaces or pairs of spaces as distinct tokens if they don't match a common pattern. Look at these two ways of sending the exact same variable payload to an LLM:</p>
<p><strong>Format A (Pretty Printed JSON):</strong></p>
<pre><code class="language-json">{
    "status": "unhealthy",
    "replica_count": 0
}
</code></pre>
<p><strong>Format B (Minified JSON):</strong></p>
<pre><code class="language-json">{"status":"unhealthy","replica_count":0}
</code></pre>
<p>Format A includes multiple newline characters and 4-space indentations. In many tokenizers (like the one used for GPT-4), Format A takes <strong>19 tokens</strong>, while Format B takes only <strong>11 tokens</strong>.</p>
<p>By stripping whitespace (minifying) before sending data to an API, you can cut your input token footprint by <strong>40% or more</strong> for large data structures like JSON logs or configuration dumps.</p>
<h2>🍓 The Missing Example: The "Strawberry" Mystery</h2>
<p>Every AI user eventually discovers that if you ask an LLM, <em>"How many 'r's are in the word strawberry?"</em>, the model will confidently answer <strong>"Two."</strong> People assume the AI is hallucinating or bad at math. But with the foundation you’ve already built in your blog, you can give your readers the real engineering answer: <strong>It is a tokenization limitation.</strong></p>
<p>You can add a section like this:</p>
<h3>🍓 Why AI Can’t Count the "R"s in Strawberry</h3>
<p>When a human reads the word "strawberry," we see a sequence of 10 letters: <code>s-t-r-a-w-b-e-r-r-y</code>.</p>
<p>But an LLM never sees the letters. Because of Byte-Pair Encoding, the tokenizer bundles the word into chunks. To the model's vocabulary, "strawberry" is split into just two tokens: <strong>["straw", "berry"]</strong>.</p>
<p>Once it gets converted into token numbers say <code>[301, 8396]</code>the raw spelling structure is completely hidden. The model is trying to count threads in a rope without untwisting it first. There is simply no "letter R" in its active memory to inspect!</p>
<ul>
<li><p><strong>Token Blindness:</strong> The LLM doesn't see the letters; it only sees two opaque boxes: <strong>[Token: "straw"]</strong> and <strong>[Token: "berry"]</strong>.</p>
</li>
<li><p><strong>The Hidden 'R':</strong> The model has no idea what letters are hidden inside the <code>"straw"</code> token. To the AI, that token is just a random identification number (like <code>301</code>). It registers <strong>0</strong> 'r's here.</p>
</li>
<li><p><strong>The Known 'R's:</strong> However, during its massive training, the model has seen plenty of text discussing the spelling of the standalone word "berry" (b-e-r-r-y). Its neural network has a strong statistical association that the <code>"berry"</code> token involves <strong>2</strong> 'r's.</p>
</li>
<li><p><strong>The Flawed Math:</strong> The model does the only math it can with the pieces it sees:</p>
</li>
</ul>
<pre><code class="language-plaintext">0 (from "straw") + 2 (from "berry") = 2
</code></pre>
<h3>Vocabulary Size (The GPT-4 vs GPT-4o Shift)</h3>
<p>When OpenAI released GPT-4o, they upgraded their tokenizer algorithm from a vocabulary size of roughly 100,000 tokens (cl100k_base) to 200,000 tokens (o200k_base).</p>
<p>By doubling the vocabulary "brick set," the model became massively more efficient, especially at processing code and non English text. For instance, the token footprint for South Asian languages like Tamil or Hindi dropped by over 60%, drastically lowering the cloud bill for developers building global apps.</p>
<h3>Special Tokens (The "Invisible" Code)</h3>
<p>It’s worth noting that not all tokens represent human language. Tokenizers include <strong>Special Tokens</strong> that act as architectural control signals. For example:</p>
<ul>
<li><p><code>&lt;|endoftext|&gt;</code> tells the model the document or prompt is officially over.</p>
</li>
<li><p><code>&lt;|im_start|&gt;</code> and <code>&lt;|im_end|&gt;</code> separate the developer’s system instructions from the user's prompt.</p>
</li>
</ul>
<p>If a user manages to trick your app into outputting or passing these special tokens unexpectedly, it can bypass security guardrails (a concept known as <em>token smuggling</em> or <em>prompt injection</em>).</p>
<h2>🚀 Going Deeper: Beyond the Basics</h2>
<p>I don't want to bombard you with more technical jargon as we are just getting started. If you are more interested, just explore these topics as well:</p>
<ul>
<li><p><strong>Token IDs (From Strings to Numbers):</strong> While we talk about tokens as text chunks (like <code>"straw"</code> or <code>"berry"</code>), the AI model itself still doesn't understand text. Once the tokenizer chops up your prompt, it maps every single token to a unique integer ID (e.g., <code>"the"</code> becomes <code>464</code>). It is this sequence of numbers that is actually passed into the neural network.</p>
</li>
<li><p><strong>The Context Window Wall:</strong> Every model has a hard limit on the total number of tokens it can hold in its memory at one time (input + output combined). If a model has a 128k token context window, exceeding that limit will cause the application to crash or forget the earliest parts of the conversation. (<em><strong>We will cover this in upcoming blogs</strong></em>)</p>
</li>
<li><p><strong>"Glitch Tokens":</strong> Because tokenizers are trained on massive web datasets, their vocabularies sometimes include highly specific, bizarre strings (like specific Reddit usernames or obscure code strings) that the actual AI model rarely saw during its training phase. When an LLM encounters these "glitch tokens," it can get confused and behave erratically!</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking : Part 5 : Gateway API Explained]]></title><description><![CDATA[In the last blog, we discussed how Ingress plays a vital role in routing external traffic into your Kubernetes cluster. If you're just joining this series or are new to Kubernetes, I'd highly recommen]]></description><link>https://claybrainer.com/kubernetes-networking-part-5-gateway-api-explained</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-part-5-gateway-api-explained</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Kubernetes-networking]]></category><category><![CDATA[Gateway API]]></category><category><![CDATA[k8]]></category><category><![CDATA[k8s]]></category><category><![CDATA[kubernetes architecture]]></category><category><![CDATA[Gateway Load Balancer]]></category><category><![CDATA[gatewayapi]]></category><category><![CDATA[kubernetes-services]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 28 Jun 2026 04:24:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/0fc3903b-ccf6-461e-adee-91daaa7a3603.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the last blog, we discussed how Ingress plays a vital role in routing external traffic into your Kubernetes cluster. If you're just joining this series or are new to Kubernetes, I'd highly recommend checking out the previous four <a href="https://claybrainer.com/series/kubernetes-in-detail">networking blogs</a> before continuing with this one.</p>
<p>That being said, if you're already familiar with Kubernetes and Kubernetes networking, grab a cup of coffee ☕ and join me as we explore the Gateway API.</p>
<h1>Why was Gateway API introduced ?</h1>
<p>Before we understand what Gateway API is, let's first answer an important question.</p>
<p><strong>Why did Kubernetes introduce another API when Ingress was already working?</strong></p>
<p>If you've read my <a href="https://claybrainer.com/kubernetes-networking-part-4-ingress-components-architecture-and-routing-strategies">previous blog</a>, you know that Ingress solved one of the biggest problems with exposing applications.</p>
<p>Without Ingress, every application needed its own <code>LoadBalancer</code> Service. In a production environment running dozens or even hundreds of applications, this quickly becomes expensive and difficult to manage.</p>
<p>Ingress solved this by allowing multiple applications to share a single Load Balancer. Using host-based and path-based routing, one Load Balancer could expose an entire fleet of applications.</p>
<p>Sounds perfect, right? Well... not exactly.</p>
<h2>The Problem with Ingress</h2>
<p>Ingress mainly consists of two components:</p>
<ul>
<li><p><strong>Ingress Resource</strong> – A Kubernetes object where you define routing rules.</p>
</li>
<li><p><strong>Ingress Controller</strong> – The component that watches those rules and configures the underlying proxy (NGINX, Cilium, Traefik, HAProxy, etc.).</p>
</li>
</ul>
<p>Technically, Ingress works very well. The challenge isn't the technology it's <strong>who manages it</strong>.</p>
<p>Let's understand this with a simple example. Imagine your company has an e-commerce application with multiple teams.</p>
<ul>
<li><p>Team A owns the <strong>User Service</strong></p>
</li>
<li><p>Team B owns the <strong>Product Service</strong></p>
</li>
<li><p>Team C owns the <strong>Payment Service</strong></p>
</li>
</ul>
<p>All of these services are exposed through a single Ingress.</p>
<p><code>shop.example.com/user -&gt; user-service</code></p>
<p><code>shop.example.com/product -&gt; product-service</code></p>
<p><code>shop.example.com/payment -&gt; payment-service</code></p>
<p>Example ingress manifest:</p>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ecommerce-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /user
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 80

      - path: /product
        pathType: Prefix
        backend:
          service:
            name: product-service
            port:
              number: 80

      - path: /payment
        pathType: Prefix
        backend:
          service:
            name: payment-service
            port:
              number: 80
</code></pre>
<p>Notice something interesting? Every application's routing configuration is stored in a <strong>single Ingress resource</strong>.</p>
<p>Now imagine the Payment team wants to expose a new endpoint. To do that, they need to modify the same Ingress resource that everyone else uses. Likewise, if the Product team wants to modify its routing, it still needs access to this shared Ingress resource.</p>
<p>This creates a few problems:</p>
<ul>
<li><p>Every team needs permission to edit the same Ingress resource.</p>
</li>
<li><p>A small mistake can accidentally impact another team's routes.</p>
</li>
<li><p>Most organizations don't allow application teams to modify shared networking resources.</p>
</li>
<li><p>As a result, every routing change has to go through the DevOps or Platform team.</p>
</li>
</ul>
<p>Now imagine dozens of teams raising tickets every day just to add or update a route.</p>
<p>Instead of developers deploying independently, they now depend on the Platform or DevOps team for even small networking changes. This slows down releases and creates an unnecessary operational bottleneck. <strong>This is one of the biggest reasons Gateway API was introduced.</strong></p>
<p><strong>It separates infrastructure ownership from application routing, allowing each team to manage only what they own while the Platform team continues to control the shared networking infrastructure.</strong></p>
<blockquote>
<p>Ingress was designed to expose applications. Gateway API was designed to expose applications at scale across multiple teams.</p>
</blockquote>
<h2><strong>What Gateway API is ?</strong></h2>
<p>Now that we understand the problem, let's see how Gateway API solves it.</p>
<p>Instead of having one Ingress resource that everyone edits, Gateway API splits the responsibilities into different resources.</p>
<p>Think of it this way:</p>
<ul>
<li><p>The <strong>Platform (DevOps) Team</strong> manages the networking infrastructure.</p>
</li>
<li><p>The <strong>Application Teams</strong> manage only their application's routing.</p>
</li>
</ul>
<p>This separation is exactly what was missing in Ingress.</p>
<p>Let's revisit our ecommerce application. Previously, we had a single Ingress like this:</p>
<p><code>shop.example.com/user -&gt; user-service</code></p>
<p><code>shop.example.com/product -&gt; product-service</code></p>
<p><code>shop.example.com/payment -&gt; payment-service</code></p>
<p>Every team had to edit the same Ingress resource whenever they wanted to expose a new endpoint.</p>
<p>With Gateway API, things work differently. The Platform team first creates a <strong>Gateway</strong>.</p>
<h3>Gateway ?</h3>
<p>Think of the Gateway as the <strong>main entry point</strong> into your Kubernetes cluster. It defines things like:</p>
<ul>
<li><p>Which ports should be exposed (HTTP/HTTPS)</p>
</li>
<li><p>Which hostname or domain should be accepted</p>
</li>
<li><p>Which teams are allowed to attach routes</p>
</li>
<li><p>TLS certificates</p>
</li>
<li><p>Listener configuration</p>
</li>
</ul>
<p><em>Notice something? There are</em> <em><strong>no application routes here</strong></em>.</p>
<p>The Platform team is responsible only for setting up the networking infrastructure. Now comes the interesting part.</p>
<p>Each application team creates its own <strong>HTTPRoute</strong>. For example,</p>
<ul>
<li><p>the User team creates: <code>/user - user-service</code></p>
</li>
<li><p>The Product team creates: <code>/product - product-service</code></p>
</li>
<li><p>etc</p>
</li>
</ul>
<p>Each team owns only its own HTTPRoute resource. If the Payment team wants to add a new endpoint tomorrow, they only modify their HTTPRoute. They don't touch the User team's routes. They don't touch the Product team's routes. They don't even need permission to edit the Gateway.</p>
<p>The Platform team continues managing the shared networking infrastructure, while every application team manages only its own routing rules.</p>
<p>This removes the operational bottleneck we discussed earlier. Developers can independently deploy routing changes without waiting for the DevOps team, and the Platform team doesn't have to review every small routing update.</p>
<p>This separation of responsibilities is one of the biggest design improvements introduced by Gateway API.</p>
<p>Now that we've learned how Gateway API solves the problems of Ingress, let's dive a little deeper and understand its core components.</p>
<h1>Components of Gateway API</h1>
<p>Gateway API defines multiple resources, but for exposing HTTP applications, there are <strong>three important resources</strong> that you'll work with most of the time.</p>
<ul>
<li><p>Gateway Class</p>
</li>
<li><p>Gateway</p>
</li>
<li><p>HTTPRoute</p>
</li>
</ul>
<p>One of the main goals of Gateway API is <strong>separation of responsibilities</strong>. Instead of having one resource that everyone edits (like Ingress), each resource has a specific purpose and is usually managed by a different team.</p>
<p>To understand these resources, let's use a simple real world analogy. Imagine your company is constructing a new office building.</p>
<ul>
<li><p>First, the architects decide <strong>which construction company</strong> will build the office.</p>
</li>
<li><p>Once that's decided and office is built, the infrastructure team designs and manages the building's <strong>main entrance</strong>, deciding <strong>who can enter</strong> and <strong>through which doors</strong>.</p>
</li>
<li><p>Finally, each department places its own <strong>signboards</strong>, directing visitors to the correct department enterance.</p>
</li>
</ul>
<p>Gateway API follows the same idea.</p>
<ul>
<li><p><strong>GatewayClass</strong> decides <strong>which gateway implementation</strong> will be used.</p>
</li>
<li><p><strong>Gateway</strong> creates the <strong>main entry point</strong> into your Kubernetes cluster.</p>
</li>
<li><p><strong>HTTPRoute</strong> defines <strong>where incoming requests should be routed.</strong></p>
</li>
</ul>
<p>Let's understand each one in detail.</p>
<h2>Gateway Class</h2>
<p>GatewayClass is the <strong>top-most resource</strong> in Gateway API.</p>
<p>Its job is to tell Kubernetes <strong>which Gateway implementation (provider)</strong> should manage your Gateways. Think of it like hiring a construction company before building your office.</p>
<p>You first decide <strong>who is going to build it</strong>. Will it be Company A? or Company B. Once you've made that decision, every building constructed under that contract follows that company's standards.</p>
<p>GatewayClass works in exactly the same way. You choose which Gateway provider will implement your Gateway resources. Some popular Gateway providers include:</p>
<ul>
<li><p><strong>Cilium Gateway</strong></p>
</li>
<li><p><strong>NGINX Gateway Fabric</strong></p>
</li>
<li><p><strong>Envoy Gateway</strong></p>
</li>
<li><p><strong>Kong Gateway</strong></p>
</li>
<li><p><strong>HAProxy Kubernetes Ingress Controller</strong></p>
</li>
<li><p><strong>Traefik</strong></p>
</li>
</ul>
<p>Each provider has its own Gateway controller that watches Gateway API resources and configures the underlying data plane accordingly.</p>
<p>For example, if you create a GatewayClass for <strong>Cilium</strong>, the Cilium Gateway controller manages all Gateways that reference that class. Likewise, if you create a GatewayClass for <strong>NGINX Gateway Fabric</strong>, the NGINX Gateway controller becomes responsible for those Gateways.</p>
<p>Since GatewayClass defines the infrastructure implementation for the entire cluster, it is a <strong>cluster scoped resource</strong> and is typically managed by the <strong>Platform or Infrastructure team</strong>.</p>
<p>Although Kubernetes allows you to create multiple GatewayClasses in the same cluster for example, one using Cilium and another using NGINX Gateway Fabric in most production environments, organizations standardize on a single provider unless there is a specific business requirement.</p>
<p>Example manifest of gateway class</p>
<pre><code class="language-yaml">apiVersion: gateway.networking.k8s.io/v1 
kind: GatewayClass 
metadata: 
  name: cilium 
spec: 
  controllerName: io.cilium/gateway-controller
</code></pre>
<p>Let's understand what each field means:</p>
<ul>
<li><p><strong>apiVersion</strong> – Specifies the version of the Gateway API.</p>
</li>
<li><p><strong>kind</strong> – Tells Kubernetes that we're creating a <code>GatewayClass</code> resource.</p>
</li>
<li><p><a href="http://metadata.name"><strong>metadata.name</strong></a> – The name of the GatewayClass. We'll reference this later while creating a <code>Gateway</code>.</p>
</li>
<li><p><strong>spec.controllerName</strong> – Identifies the Gateway controller that will manage this GatewayClass. Since we're using Cilium, the controller name is <code>io.cilium/gateway-controller</code>.</p>
</li>
</ul>
<h2>Gateway</h2>
<p>Now that we've selected our Gateway provider using a <strong>GatewayClass</strong>, the next step is to create a <strong>Gateway</strong>.</p>
<p>Continuing with our office building analogy...</p>
<p>We've already decided which construction company is going to build and manage our building. Now, it's time to design the <strong>main entrance</strong>. Think of the Gateway as the <strong>main entrance</strong> to your office building.</p>
<p>The <strong>infrastructure team</strong> decides:</p>
<ul>
<li><p>Which entrances should be open (HTTP, HTTPS, or both)</p>
</li>
<li><p>Which domain names should visitors use</p>
</li>
<li><p>Which TLS certificate should secure the entrance</p>
</li>
<li><p>Which application teams are allowed to attach their routes</p>
</li>
</ul>
<p>Notice that the Gateway <strong>doesn't know anything about your applications</strong>. It doesn't know where the User Service is. It simply defines <strong>how traffic is allowed to enter the Kubernetes cluster</strong>.</p>
<p>Once the Gateway is created, multiple application teams can attach their own routing rules without modifying the Gateway itself. <em>This is one of the biggest improvements over Ingress.</em></p>
<p>With Ingress, networking configuration and application routing were often defined in the same resource. With Gateway API, those responsibilities are separated.</p>
<p>The Platform or Infrastructure team owns the <strong>Gateway</strong>, while application teams own their <strong>HTTPRoute</strong> resources. This allows each team to work independently without interfering with one another.</p>
<p>Let's look at what a Gateway resource looks like.</p>
<pre><code class="language-yaml">apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: web-gateway
spec:
  gatewayClassName: cilium
  listeners:
  - name: http
    protocol: HTTP
    port: 80
</code></pre>
<p>Let's understand the important fields in the Gateway manifest.</p>
<ul>
<li><p><strong>apiVersion</strong> – Specifies the version of the Gateway API that Kubernetes should use.</p>
</li>
<li><p><strong>kind</strong> – Indicates that we're creating a <code>Gateway</code> resource.</p>
</li>
<li><p><a href="http://metadata.name"><strong>metadata.name</strong></a> – The name of the Gateway. Other resources, such as <code>HTTPRoute</code>, will reference this Gateway to attach their routing rules.</p>
</li>
<li><p><strong>gatewayClassName</strong> – Specifies which <code>GatewayClass</code> should manage this Gateway. In our example, we're using the <code>cilium</code> GatewayClass that we created earlier. This tells Kubernetes to let the Cilium Gateway controller manage this Gateway.</p>
</li>
<li><p><strong>listeners</strong> – Defines how the Gateway accepts incoming traffic. A Gateway can have one or more listeners.</p>
<p>In our example, the listener is configured with:</p>
<ul>
<li><p><strong>name</strong> – A unique name for the listener.</p>
</li>
<li><p><strong>protocol</strong> – The protocol that the Gateway accepts, such as <code>HTTP</code>, <code>HTTPS</code>, <code>TLS</code>, or <code>TCP</code>.</p>
</li>
<li><p><strong>port</strong> – The port on which the Gateway listens for incoming traffic.</p>
</li>
</ul>
</li>
</ul>
<h2>HTTPRoute</h2>
<p>So far, we've selected our Gateway provider using a <strong>GatewayClass</strong> and created a <strong>Gateway</strong> to act as the main entry point into our Kubernetes cluster.</p>
<p>But there's still one important question. <strong>Once a request enters the cluster, how does it know which application to reach?</strong></p>
<p>This is where <strong>HTTPRoute</strong> comes into the picture.</p>
<p>Let's continue with our office building analogy.</p>
<p>A visitor enters the building through the main entrance. Now they need directions. They need to go to the Finance department which room they want to enter ?</p>
<p>This is exactly what the signboards inside a building do—they guide visitors to the correct destination.</p>
<p>An <strong>HTTPRoute</strong> works in the same way. It defines how incoming HTTP requests should be matched and which backend service they should be forwarded to.</p>
<p>Unlike Ingress, each application team can create and manage its own HTTPRoute without modifying the shared Gateway. For our ecommerce application, each team owns its own HTTPRoute.</p>
<p>The User team creates an HTTPRoute for <code>/user</code>.</p>
<p>The Product team creates an HTTPRoute for <code>/product</code>.</p>
<p>The Payment team creates an HTTPRoute for <code>/payment</code>.</p>
<p>Each team is responsible only for its own routing configuration. This separation allows multiple teams to work independently while sharing the same Gateway.</p>
<p>Let's look at an example HTTPRoute.</p>
<pre><code class="language-yaml">apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: user-route
spec:
  parentRefs:
  - name: web-gateway

  hostnames:
  - shop.example.com

  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /user

    backendRefs:
    - name: user-service
      port: 80
</code></pre>
<p>Let's understand the important fields:</p>
<ul>
<li><p><a href="http://metadata.name"><strong>metadata.name</strong></a> – The name of the HTTPRoute resource.</p>
</li>
<li><p><strong>parentRefs</strong> – Specifies which Gateway this route should attach to. In our example, this route is attached to the <code>web-gateway</code>.</p>
</li>
<li><p><strong>hostnames</strong> – Defines the hostname that this route should match.</p>
</li>
<li><p><strong>rules</strong> – Contains one or more routing rules.</p>
</li>
<li><p><strong>matches</strong> – Specifies the conditions that incoming requests must satisfy. Here, we're matching requests whose path starts with <code>/user</code>.</p>
</li>
<li><p><strong>backendRefs</strong> – Specifies the backend Service that should receive the traffic once the request matches the rule.</p>
</li>
</ul>
<h2>Overall Flow</h2>
<ul>
<li><p>A request to <code>shop.example.com/user/profile</code></p>
</li>
<li><p>Arrives at the <strong>Gateway</strong></p>
</li>
<li><p>The Gateway forwards it to the attached <strong>HTTPRoute</strong></p>
</li>
<li><p>The HTTPRoute matches the <code>/user</code> path</p>
</li>
<li><p>The request is finally sent to the <code>user-service</code></p>
</li>
</ul>
<h1>Hands On Lab</h1>
<p>We've covered the concepts behind Gateway API and understood how its components work together. But the best way to learn is by seeing it in action.</p>
<p>In this hands on lab, we'll deploy a Gateway, create an HTTPRoute, and expose our application using Gateway API.</p>
<p>Feel free to follow along using the <strong>README</strong> in the <a href="https://github.com/naveenkumarvr/kubernetes_learning/tree/main/networking/03-gateway-api">GitHub repository</a>. By the end of this lab, you'll have a working Gateway API setup and a much clearer understanding of everything we've learned so far.</p>
<p>Let's get our hands dirty!</p>
<h1>Conclusion</h1>
<p>Congratulations! 🎉 If you've made it this far.</p>
<p>In this blog, we learned why the Kubernetes community introduced Gateway API and how it addresses some of the limitations of Ingress, especially in environments where multiple teams share the same cluster.</p>
<p>We explored the three core resources you'll use most often:</p>
<ul>
<li><p><strong>GatewayClass</strong> – Specifies which Gateway provider manages your Gateways.</p>
</li>
<li><p><strong>Gateway</strong> – Defines how traffic enters your Kubernetes cluster.</p>
</li>
<li><p><strong>HTTPRoute</strong> – Defines where that traffic should be routed.</p>
</li>
</ul>
<p>Most importantly, we learned that Gateway API isn't just another way to expose applications. It introduces a clear separation of responsibilities, allowing Platform teams to manage the networking infrastructure while application teams independently manage their own routing rules.</p>
<p>This makes Gateway API more scalable, easier to manage, and a better fit for modern production environments.</p>
<p>I hope this blog helped you understand not just <strong>how</strong> Gateway API works, but also <strong>why</strong> it was introduced in the first place.</p>
<p>In the next blog, we'll dive deeper into the networking concepts of kubernetes.</p>
<p>Until then, keep learning, keep experimenting, and as always...</p>
<p><strong>Happy Kubernetes! 🚀</strong></p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking : Part 4 : Ingress: Components, Architecture, and Routing Strategies]]></title><description><![CDATA[In our last blog, we explored different ways to expose applications running inside a Kubernetes cluster to the outside world. If you're just joining this series or are new to Kubernetes, I highly reco]]></description><link>https://claybrainer.com/kubernetes-networking-part-4-ingress-components-architecture-and-routing-strategies</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-part-4-ingress-components-architecture-and-routing-strategies</guid><category><![CDATA[k8s-series]]></category><category><![CDATA[k8s]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubernetes-services]]></category><category><![CDATA[kubernetes ingress]]></category><category><![CDATA[#Kubernetes #Ingress #DevOps #kind #NGINX #Microservices #GitOps]]></category><category><![CDATA[Kubernetes Ingress Controller]]></category><category><![CDATA[ingress]]></category><category><![CDATA[Ingress Controllers]]></category><category><![CDATA[IngressController]]></category><category><![CDATA[Ingress Controller]]></category><category><![CDATA[Traefik]]></category><category><![CDATA[ingress resources]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 21 Jun 2026 05:57:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/53dc771f-aeae-485b-a6eb-25655e318f80.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In our <a href="https://claybrainer.com/kubernetes-networking-part-3-how-to-expose-applications-to-the-outside-world">last blog</a>, we explored different ways to expose applications running inside a Kubernetes cluster to the outside world. If you're just joining this series or are new to Kubernetes, I highly recommend going through the <a href="https://claybrainer.com/series/kubernetes-in-detail">previous blogs</a>. They will give you a solid understanding of the networking concepts we'll be building upon in this article.</p>
<p>That being said, if you already have a basic understanding of Kubernetes and networking, grab a cup of coffee ☕ and let's explore how Ingress works in Kubernetes.</p>
<h1>Ingress Basics:</h1>
<p>This is probably the <strong>most common and heavily used method in production environments</strong> to expose applications to the outside world.</p>
<p>At this point, we already know that Kubernetes provides multiple ways to expose our applications.</p>
<h3>NodePort</h3>
<p>With NodePort, applications are exposed through a port on every cluster node.</p>
<p>While it is great for learning and testing, managing NodePorts at scale can quickly become painful. This is one of the reasons why NodePort is rarely used directly in production environments.</p>
<h3>LoadBalancer</h3>
<p>A LoadBalancer Service solves many of the limitations of NodePort. The cloud provider automatically provisions a Load Balancer and routes traffic to your application.</p>
<p>Sounds great, right? But there is one problem.</p>
<p><strong>Every application exposed using a LoadBalancer Service typically gets its own cloud Load Balancer. As the number of applications grows, so does the cost 💸.</strong></p>
<p>Imagine running 20 applications and provisioning 20 separate Load Balancers. Your cloud bill might not be very happy.</p>
<h3>Ingress</h3>
<p>Instead of creating a dedicated Load Balancer for every application, we can place a single Ingress Controller in front of multiple applications.</p>
<p>Typically, a single cloud Load Balancer forwards all incoming traffic to the Ingress Controller. The Ingress Controller then examines the incoming request and decides where it should go based on the routing rules we define.</p>
<p>For example:</p>
<ul>
<li><p>Requests for <a href="http://shop.example.com"><code>shop.example.com</code></a> can be routed to the shopping application.</p>
</li>
<li><p>Requests for <a href="http://api.example.com"><code>api.example.com</code></a> can be routed to the API service.</p>
</li>
<li><p>Requests for <code>/admin</code> can be routed to a completely different backend.</p>
</li>
</ul>
<p>In simple terms, the Ingress Controller acts as the gatekeeper between the outside world and your Kubernetes cluster.</p>
<p>One important things to keep in mind that,</p>
<blockquote>
<p><strong>Ingress routes traffic to Services, not directly to Pods.</strong></p>
</blockquote>
<h2>Components of Ingress</h2>
<p>When people talk about Ingress, they are usually referring to two different components:</p>
<ol>
<li><p>Ingress</p>
</li>
<li><p>Ingress Controller</p>
</li>
</ol>
<p>Let's understand the difference because this is where many beginners get confused.</p>
<h3>Ingress</h3>
<p>Ingress is a Kubernetes resource, just like a Deployment or a Service.</p>
<p>It contains the routing rules that describe:</p>
<ul>
<li><p>Which hostname should map to which Service</p>
</li>
<li><p>Which URL path should route to which application</p>
</li>
<li><p>How incoming traffic should be handled</p>
</li>
</ul>
<p>Think of an Ingress resource as a traffic routing configuration file.</p>
<p>By itself, the Ingress resource does absolutely nothing. It simply stores the desired routing rules.</p>
<h3>Ingress Controller</h3>
<p>The Ingress Controller is the actual application that reads the Ingress configuration and performs the routing.</p>
<p>Without an Ingress Controller, creating an Ingress resource is like writing traffic rules on a piece of paper and expecting cars to follow them automatically.</p>
<p>Some popular Ingress Controllers include:</p>
<ul>
<li><p><strong>NGINX Ingress Controller</strong> (one of the most widely used options, but officially retiring in March 2026)</p>
</li>
<li><p><strong>Traefik</strong></p>
</li>
<li><p><strong>HAProxy</strong></p>
</li>
<li><p><strong>Istio Ingress Gateway</strong></p>
</li>
<li><p><strong>Kong Ingress Controller</strong></p>
</li>
</ul>
<blockquote>
<p><strong>Note:</strong> The NGINX Ingress Controller project announced its retirement and reached end-of-life in March 2026. New Kubernetes environments are increasingly adopting alternatives such as Gateway API implementations.</p>
</blockquote>
<p>The Ingress Controller does much more than simple traffic routing. Depending on the implementation, it can also provide:</p>
<ul>
<li><p><strong>TLS Termination:</strong> Users access applications securely using HTTPS, while Ingress handles SSL/TLS for us.</p>
</li>
<li><p><strong>Authentication Integrations:</strong> Can integrate with OAuth, SSO, Identity Providers, and more.</p>
</li>
<li><p><strong>Rate Limiting:</strong> Prevent abuse by limiting requests. i.e Stop someone from sending 10 million requests in 5 minutes 😅</p>
</li>
<li><p><strong>Traffic Filtering:</strong> Block unwanted traffic based on headers, IPs, rules, etc.</p>
</li>
<li><p><strong>Traffic redirection:</strong> Redirect traffic to different endpoints based on the configuration</p>
</li>
<li><p><strong>Observability and metrics:</strong> Provides network related Metrics and logs for observability such as API response time, etc</p>
</li>
</ul>
<p>In short, an Ingress Controller is much more than a router—it's often the first line of traffic management and security for applications running inside a Kubernetes cluster.</p>
<h1><strong>How Does Traffic Enter the Cluster?</strong></h1>
<p>Now that we understand what an Ingress Controller is, let's see how traffic actually flows through the cluster.</p>
<p><code>Internet → External Load Balancer → Ingress Controller → Application Service → Pod</code></p>
<p>In simple terms:</p>
<ol>
<li><p>A user sends a request from the internet.</p>
</li>
<li><p>The request reaches the external Load Balancer.</p>
</li>
<li><p>The Load Balancer forwards the request to the Ingress Controller.</p>
</li>
<li><p>The Ingress Controller examines the request and determines where it should go.</p>
</li>
<li><p>The request is forwarded to the appropriate Kubernetes Service.</p>
</li>
<li><p>Finally, the Service routes the request to one of the application's Pods.</p>
</li>
</ol>
<p>So the real question becomes:</p>
<p><strong>How does the Ingress Controller know where to send the request?</strong> Great question 😄</p>
<h2>How Does Ingress Know Where to Route Traffic?</h2>
<p>We define routing behavior using an <strong>Ingress resource</strong>.</p>
<p>Just like Deployments define how Pods should run and Services define how applications should be exposed internally, <strong>an Ingress resource defines how external traffic should be routed.</strong></p>
<p>Typically, <strong>an Ingress manifest is deployed alongside the application's Deployment and Service resources.</strong></p>
<p>The Ingress resource tells Kubernetes:</p>
<ul>
<li><p>Which hostname should be matched</p>
</li>
<li><p>Which URL path should be matched</p>
</li>
<li><p>Which Service should receive the traffic</p>
</li>
<li><p>Which Ingress Controller should process the request</p>
</li>
</ul>
<p><strong>The Ingress Controller continuously watches these Ingress resources and updates its routing configuration accordingly.</strong></p>
<h2>Traffic Routing Strategies</h2>
<p>Ingress commonly routes traffic using two strategies:</p>
<ol>
<li><p>Path-Based Routing</p>
</li>
<li><p>Host-Based Routing</p>
</li>
</ol>
<p>Let's look at both with real-world examples.</p>
<h3>Path-Based Routing</h3>
<p>In Path Based Routing, multiple applications share the same hostname, but different URL paths are routed to different services.</p>
<p>Imagine we have an ecommerce platform with three applications:</p>
<ul>
<li><p>Frontend Service</p>
</li>
<li><p>Product Service</p>
</li>
<li><p>Admin Service</p>
</li>
</ul>
<p>We want all traffic to come through the same domain: <code>example.com</code></p>
<p>But route requests based on the URL path.</p>
<pre><code class="language-plaintext">example.com/products   → Product Service
example.com/admin      → Admin Service
example.com/           → Frontend Service
</code></pre>
<p>Visually:</p>
<pre><code class="language-plaintext">                   example.com
                         │
                         ▼
                Ingress Controller
                    │     │     │
                    │     │     │
                    ▼     ▼     ▼
                /       /products   /admin
                 │           │         │
                 ▼           ▼         ▼
          Frontend     Product Svc   Admin Svc
</code></pre>
<p>This approach is useful when multiple applications belong to the same domain but serve different functions.</p>
<h3>Host-Based Routing</h3>
<p>In Host Based Routing, traffic is routed based on the hostname requested by the user.</p>
<p>For example:</p>
<pre><code class="language-plaintext">shop.example.com   → Shopping Service
api.example.com    → API Service
admin.example.com  → Admin Service
</code></pre>
<p>The Ingress Controller examines the <code>Host</code> header in the incoming request and forwards traffic to the appropriate Service.</p>
<p>Visually:</p>
<pre><code class="language-plaintext">                Ingress Controller
                       │
       ┌───────────────┼────
       │               │               │
       ▼              ▼              ▼

shop.example.com  api.example.com  admin.example.com
       │               │               │
       ▼              ▼              ▼

 Shopping Svc      API Svc        Admin Svc
</code></pre>
<p>This is one of the most common routing patterns used in production environments because different applications can have their own dedicated subdomains while still sharing the same Ingress Controller and Load Balancer.</p>
<p>In practice, most production environments use a combination of both Host-Based and Path-Based Routing. For example:</p>
<pre><code class="language-plaintext">api.example.com/v1      → API v1 Service
api.example.com/v2      → API v2 Service
shop.example.com        → Shopping Service
admin.example.com       → Admin Service
</code></pre>
<p>This provides a flexible and cost-effective way to expose multiple applications without provisioning a separate Load Balancer for each one.</p>
<h1>Enough Theory, Let's Build It!</h1>
<p>So far, we've covered a lot of concepts around Ingress, Ingress Controllers, and traffic routing. But as engineers, we all know one thing:</p>
<blockquote>
<p>The real learning starts when we get our hands dirty. 😄</p>
</blockquote>
<p>Let's move beyond the theory and see Ingress in action.</p>
<p>For this demo, we'll use <strong>Traefik</strong>, one of the most popular Kubernetes Ingress Controllers. We'll deploy Traefik, expose a couple of sample applications, and then configure Ingress rules to route traffic based on hostnames and URL paths.</p>
<p>Rather than walking through a simple demo here, I've put together a dedicated hands-on lab repository where you can practice everything we've learned in this blog.</p>
<p>The repository includes step-by-step exercises covering:</p>
<ul>
<li><p>Installing and configuring Traefik</p>
</li>
<li><p>Creating Ingress resources</p>
</li>
<li><p>Path-Based Routing</p>
</li>
<li><p>Host-Based Routing</p>
</li>
<li><p>TLS configuration</p>
</li>
<li><p>Traffic validation and troubleshooting</p>
</li>
<li><p>Real-world Ingress scenarios</p>
</li>
</ul>
<p>👉 <a href="https://github.com/naveenkumarvr/kubernetes_learning/tree/main/networking/02-ingress">Follow along using the hands-on labs</a></p>
<p>My recommendation is simple: don't just read the manifests deploy them, modify them, break them, and fix them. That's the fastest way to understand how Ingress works under the hood.</p>
<h1>Conclusion</h1>
<p>In this blog, we explored one of the most important concepts in Kubernetes networking: <strong>Ingress</strong>.</p>
<p>We started by understanding why Ingress was introduced and the challenges it solves compared to NodePort and LoadBalancer Services. We then learned the two key components involved:</p>
<ul>
<li><p>Ingress Resource</p>
</li>
<li><p>Ingress Controller</p>
</li>
</ul>
<p>We also looked at how traffic flows from the internet into a Kubernetes cluster and explored two common routing strategies:</p>
<ul>
<li><p>Path-Based Routing</p>
</li>
<li><p>Host-Based Routing</p>
</li>
</ul>
<p>By now, you should have a solid understanding of how a single Ingress Controller can intelligently route traffic to multiple applications while reducing infrastructure costs and simplifying traffic management.</p>
<p>Of course, what we've covered here is just the beginning. Modern Ingress Controllers such as Traefik provide many advanced capabilities that are commonly used in production environments.</p>
<p>Some great next steps to explore are:</p>
<ul>
<li><p><strong>TLS/SSL</strong> – Secure your applications with HTTPS and TLS certificates.</p>
</li>
<li><p><strong>Authentication</strong> – Protect applications using Basic Authentication, OAuth, or external identity providers.</p>
</li>
<li><p><strong>Rate Limiting</strong> – Prevent abuse and protect services from excessive traffic.</p>
</li>
<li><p><strong>Canary Deployments</strong> – Gradually roll out new versions using weighted traffic routing.</p>
</li>
<li><p><strong>Monitoring &amp; Observability</strong> – Integrate Prometheus and Grafana to monitor traffic patterns and controller health.</p>
</li>
</ul>
<p>Most importantly, don't stop with the theory. Take the examples from the hands on repository, experiment with them, break them intentionally, and troubleshoot the issues. That's where Kubernetes networking truly starts to make sense.</p>
<p>Until then, happy learning and happy Kubernetes-ing! 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking : Part 3 : How to Expose Applications to the Outside World]]></title><description><![CDATA[In our previous blogs, we explored how Kubernetes networking is structured, the role of Services, and the different types of Services Kubernetes offers.
If you are new to this series, I highly recomme]]></description><link>https://claybrainer.com/kubernetes-networking-part-3-how-to-expose-applications-to-the-outside-world</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-part-3-how-to-expose-applications-to-the-outside-world</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Kubernetes-networking]]></category><category><![CDATA[#kubernetesNetworking]]></category><category><![CDATA[Kubernetes Networking Deep Dive]]></category><category><![CDATA[kubernetes-services]]></category><category><![CDATA[kubernetes ingress]]></category><category><![CDATA[kubernetes gateway api]]></category><category><![CDATA[Kubernetes nodeport]]></category><category><![CDATA[kubernetes-load-balancer]]></category><category><![CDATA[kubernetes setup]]></category><category><![CDATA[kubernetes architecture]]></category><category><![CDATA[kubernetes-networking-tools]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 14 Jun 2026 08:11:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/b4a1bb92-da56-4e60-ac37-0a3cc38269e5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In our previous blogs, we explored how Kubernetes networking is structured, the role of Services, and the different types of Services Kubernetes offers.</p>
<p>If you are new to this series, I highly recommend checking out the <a href="https://claybrainer.com/series/kubernetes-in-detail">previous networking blogs</a> first. At least having a basic understanding of Kubernetes networking and Services will help you follow along much better in this blog. That said, if you already have some experience and know the basics of networking and Kubernetes Services, feel free to continue from here 🚀</p>
<p>In this blog, we will explore one of the most important topics in Kubernetes networking:</p>
<blockquote>
<p>How do we expose applications to the outside world?</p>
</blockquote>
<p>Because let’s be honest 😄 Running an application inside a Kubernetes cluster is one thing. But making it accessible to users securely and efficiently? That is where things start getting interesting.</p>
<p>We will explore the different ways Kubernetes allows us to expose applications, including:</p>
<ul>
<li><p><strong>NodePort</strong> — the simplest way to expose applications</p>
</li>
<li><p><strong>LoadBalancer</strong> — cloud-native exposure using external Load Balancers</p>
</li>
<li><p><strong>Ingress</strong> — the production-friendly and cost-effective approach</p>
</li>
<li><p><strong>Gateway API</strong> — the modern evolution of Kubernetes traffic management</p>
</li>
</ul>
<p>We will also discuss:</p>
<ul>
<li><p>When to use each option</p>
</li>
<li><p>Their pros and cons</p>
</li>
<li><p>Why some approaches work better than others in production environments</p>
</li>
</ul>
<p>By the end of this blog, you will have a solid understanding of the different ways applications are exposed in Kubernetes and when to choose each approach.</p>
<p>So grab your coffee ☕, let’s get started!</p>
<h1>How Do We Expose Applications to the Outside World?</h1>
<p>Let’s start with the most basic and simplest approach.</p>
<h2>1. Node Port:</h2>
<p>This is one of the simplest ways to expose your application to the outside world.</p>
<h3>What it is and How it works ?</h3>
<p>As the name suggests, Kubernetes exposes your application using a <strong>port on the cluster node itself</strong>.</p>
<p>Here, we use a <strong>Service of type</strong> <code>NodePort</code>. Kubernetes opens a specific port on <strong>every node in the cluster</strong>, and traffic hitting that port gets forwarded to your application.</p>
<p>For example, imagine your application is exposed on port <code>30080</code>. This means: <code>&lt;NodeIP&gt;:30080</code> can be used to access your application.</p>
<p>One important thing to remember here is: Even if your application is running only on <strong>one node</strong>, Kubernetes exposes the same port on <strong>all nodes</strong> in the cluster.</p>
<p>So what happens if traffic reaches the “wrong” node where your application is not running? No worries our friend <strong>kube-proxy</strong> takes care of this for us 🚀</p>
<ul>
<li><p><code>kube-proxy</code> is responsible for forwarding the traffic to the correct Pod, even if the request initially lands on a node where your application is not hosted.</p>
</li>
<li><p>In short: <code>Request → Any Cluster Node → kube-proxy → Correct Pod</code></p>
</li>
</ul>
<p>Sounds simple, right? Well… yes, but there are a few catches 😄. You will get to know in Cons section.</p>
<h3>Pros</h3>
<ul>
<li><p>Easy to implement</p>
</li>
<li><p>Useful for local testing and quick demos</p>
</li>
<li><p>No external Load Balancer required, making it cost-effective</p>
</li>
</ul>
<h3>Cons</h3>
<ul>
<li><p>Not ideal for production grade deployments</p>
</li>
<li><p>Port conflicts can become a headache</p>
</li>
<li><p>Managing and remembering ports is difficult as applications grow</p>
</li>
<li><p>Exposes high numbered ports, which is not very user friendly</p>
</li>
<li><p>Rarely used in production; mostly useful for local testing or debugging</p>
</li>
</ul>
<p>So while <strong>NodePort</strong> is great to get started, it is usually <strong>not the preferred approach for production environments</strong>. And this is where more advanced approaches like <strong>LoadBalancer, Ingress, and eventually Gateway API</strong> start becoming important.</p>
<h2>2. Load Balancer</h2>
<p>After NodePort, the next common option to expose applications is using a <strong>LoadBalancer Service</strong>. This is one of the more <strong>cloud native</strong> ways of exposing applications to the outside world.</p>
<p>But there is one catch here 👀</p>
<p>This approach mainly works when your Kubernetes cluster is hosted on a <strong>cloud provider</strong> such as AWS, Azure, or GCP because Kubernetes depends on the cloud provider to provision an external Load Balancer for you.</p>
<h3>What is it and How does it work?</h3>
<p>The idea here is pretty straightforward.</p>
<p>Instead of exposing your application directly through a node port, Kubernetes creates a <strong>Cloud Provider Load Balancer</strong> and connects external traffic to your application through it.</p>
<p>For this, we use a <strong>Service of type</strong> <code>LoadBalancer</code>.</p>
<p>Once you create a <code>LoadBalancer</code> Service, Kubernetes communicates with the cloud provider and automatically provisions a Load Balancer for your application.</p>
<p>Behind the scenes, the flow looks something like this:</p>
<p><code>Internet → Cloud Load Balancer → Kubernetes Service → Pod</code></p>
<p>For example:</p>
<ul>
<li><p>In AWS, Kubernetes can provision an <strong>NLB (Network Load Balancer)</strong> by default</p>
</li>
<li><p>In Azure, it creates an <strong>Azure Load Balancer</strong></p>
</li>
<li><p>In GCP, it provisions a <strong>Google Cloud Load Balancer</strong></p>
</li>
</ul>
<p>The best part?</p>
<p>You don’t have to manually create or configure the Load Balancer yourself. Kubernetes handles it automatically (provided your cluster has the necessary permissions).</p>
<p>Sounds perfect, right? Well… not exactly 😄</p>
<p>Here comes the biggest downside.</p>
<h3>The Cost Problem 💸</h3>
<p>For <strong>every application</strong> exposed using a <code>LoadBalancer</code> Service, Kubernetes generally creates a <strong>separate external Load Balancer</strong>.</p>
<p>Let’s say:</p>
<ul>
<li><p>You have <strong>10 applications</strong> → you may end up with <strong>10 Load Balancers</strong></p>
</li>
<li><p>You have <strong>100 applications</strong> → yes, potentially <strong>100 Load Balancers</strong> 😅</p>
</li>
</ul>
<p>And as we all know, cloud Load Balancers are <strong>not cheap</strong>. This makes the approach expensive and difficult to scale for large environments.</p>
<h3>Pros</h3>
<ul>
<li><p>Easy to implement</p>
</li>
<li><p>Production-ready and widely used</p>
</li>
<li><p>Direct real-world connectivity to applications</p>
</li>
<li><p>Secure when configured properly (TLS, security groups, firewall rules, etc.)</p>
</li>
</ul>
<h3>Cons</h3>
<ul>
<li><p>Can become <strong>very expensive</strong> at scale</p>
</li>
<li><p>Each application may get its own Load Balancer</p>
</li>
<li><p>Managing many Load Balancers becomes operationally difficult</p>
</li>
<li><p>Less cost-efficient for microservices architectures with many apps</p>
</li>
</ul>
<p>So while <code>LoadBalancer</code> Services are great and production ready, using them for every application is usually <strong>not the most scalable or cost effective approach</strong>.</p>
<p>And this is exactly where solutions like <strong>Ingress</strong> and later <strong>Gateway API</strong> start becoming really interesting 🚀</p>
<h2>3. Ingress:</h2>
<p>This is probably the <strong>most common and heavily used method in production environments</strong> to expose applications to the outside world.</p>
<p>In the previous section, we saw one major problem with <code>LoadBalancer</code> Services: <strong>Every application gets its own Load Balancer.</strong> And as we know, cloud Load Balancers are expensive 💸</p>
<p>So naturally, the question becomes:</p>
<blockquote>
<p>What if we use <strong>one Load Balancer for multiple applications</strong>?</p>
</blockquote>
<p>Well… that is exactly the problem <strong>Ingress</strong> solves 😄</p>
<h3>What is it and How does it work?</h3>
<p>Before we understand <strong>Ingress</strong>, there is one important thing to clarify.</p>
<p><strong>Ingress ≠ Ingress Controller</strong></p>
<p>These are two different things.</p>
<h3>Ingress:</h3>
<p><code>Ingress</code> is a Kubernetes resource (just like Deployment or Service).</p>
<p>It contains the routing rules:</p>
<ul>
<li><p>Which application should receive traffic?</p>
</li>
<li><p>Which hostname should map to which service?</p>
</li>
<li><p>Which path should go to which application?</p>
</li>
</ul>
<p>Think of it as a <strong>traffic routing configuration file</strong>.</p>
<h3>Ingress Controller</h3>
<p>The <strong>Ingress Controller</strong> is the actual application that reads the Ingress configuration and performs the routing. Instead of relying completely on cloud native routing logic, the Ingress Controller uses technologies like:</p>
<ul>
<li><p><strong>NGINX Ingress Controller</strong> (one of the most widely used options, but officially retiring in March 2026)</p>
</li>
<li><p><strong>Traefik</strong></p>
</li>
<li><p><strong>HAProxy</strong></p>
</li>
<li><p><strong>Istio Ingress Gateway</strong></p>
</li>
<li><p>And many more...</p>
</li>
</ul>
<p>These tools take care of <strong>traffic routing, TLS termination, filtering, rate limiting, and much more</strong>.</p>
<blockquote>
<p><strong>Note:</strong> The Kubernetes community announced the retirement of the <strong>Ingress-NGINX</strong> project. Best-effort maintenance continues until <strong>March 2026</strong>, after which there will be <strong>no new releases, bug fixes, or security updates</strong>. Existing deployments will continue to work, but organizations are encouraged to plan migrations to alternatives such as Gateway API-compatible solutions.</p>
</blockquote>
<h3>But Wait… How Does Traffic Enter the Cluster?</h3>
<p>Good question 👀</p>
<p>You might remember:</p>
<blockquote>
<p>By default, applications inside Kubernetes use <strong>ClusterIP</strong>, which cannot be accessed from outside the cluster.</p>
</blockquote>
<p>So how does Ingress solve this? Here is the trick:</p>
<p>The <strong>Ingress Controller itself is exposed using a</strong> <code>LoadBalancer</code> <strong>Service</strong>. That means: Instead of creating <strong>100 Load Balancers for 100 applications</strong>, we create:</p>
<p><code>1 Load Balancer → 1 Ingress Controller → Many Applications</code></p>
<p>Much cheaper 😄</p>
<p>The traffic flow looks like this:</p>
<p><code>Internet → External Load Balancer → Ingress Controller → Application Service → Pod</code></p>
<p>So all incoming traffic enters through the <strong>Ingress Controller</strong>, and then the controller decides: <strong>Which application should receive this request?</strong></p>
<p><strong>How Does Ingress Know Where to Route Traffic?</strong> Another great question 😄</p>
<p>We define this using an <strong>Ingress resource</strong> which is deployed along with the application resources such as deployment or service as one of the resource. Each application can have its own Ingress manifest, which tells Kubernetes:</p>
<ul>
<li><p>Which hostname to listen for</p>
</li>
<li><p>Which path to match</p>
</li>
<li><p>Which service should receive traffic</p>
</li>
<li><p>Which Ingress Controller to use</p>
</li>
</ul>
<p>Traffic routing in an Ingress is typically handled in two ways: <strong>Path based Routing</strong> and <strong>Host based Routing</strong>. These routing strategies help direct incoming requests to the appropriate services based on the URL path or hostname.</p>
<p>For now, just understand that an Ingress routes incoming traffic to the correct application using one of these techniques. We’ll explore both routing methods in more detail in a separate blog post.</p>
<h3>Simple Ingress Manifest Example</h3>
<pre><code class="language-yaml"># API Version of the Ingress resource
apiVersion: networking.k8s.io/v1

# We are creating an Ingress object
kind: Ingress

metadata:
  # Name of the Ingress
  name: demo-ingress

spec:
  # Which ingress controller should handle this
  ingressClassName: nginx

  rules:
    # Hostname to match
    - host: app.example.com
      http:
        paths:
          # URL path to match
          - path: /

            # Match everything under "/"
            pathType: Prefix

            backend:
              service:
                # Service to route traffic to
                name: my-app-service

                port:
                  # Service port number
                  number: 80
</code></pre>
<p>What this manifest says is: <strong>If traffic comes to</strong> <a href="http://app.example.com"><code>app.example.com</code></a><strong>, send it to</strong> <code>my-app-service</code> <strong>on port</strong> <code>80</code><strong>.</strong></p>
<h3>What Else Can Ingress Do?</h3>
<p>Ingress is not just about routing traffic. It also provides many production grade features like:</p>
<ul>
<li><p><strong>TLS Termination:</strong> Users access applications securely using HTTPS, while Ingress handles SSL/TLS for us.</p>
</li>
<li><p><strong>Rate Limiting:</strong> Prevent abuse by limiting requests. i.e Stop someone from sending 10 million requests in 5 minutes 😅</p>
</li>
<li><p><strong>Traffic Filtering:</strong> Block unwanted traffic based on headers, IPs, rules, etc.</p>
</li>
<li><p><strong>Authentication Integrations:</strong> Can integrate with OAuth, SSO, Identity Providers, and more.</p>
</li>
</ul>
<p>Basically, Ingress becomes the <strong>front door of your Kubernetes cluster</strong> 🚪</p>
<h2>Pros</h2>
<ul>
<li><p>Very cost-effective compared to individual LoadBalancers</p>
</li>
<li><p>One external Load Balancer can serve many applications</p>
</li>
<li><p>Production-ready and widely adopted</p>
</li>
<li><p>Supports TLS termination</p>
</li>
<li><p>Supports rate limiting and traffic filtering</p>
</li>
<li><p>Multiple controller options available</p>
</li>
</ul>
<h2>Cons</h2>
<ul>
<li><p>One component ends up handling many responsibilities</p>
</li>
<li><p>Can become a bottleneck if not designed properly</p>
</li>
<li><p>If the Ingress Controller fails, many applications may be affected</p>
</li>
<li><p>Limited delegation model — platform teams manage controllers while developers often need routing changes, causing dependency</p>
</li>
</ul>
<p>And this exact limitation around <strong>ownership, flexibility, and delegation</strong> is one of the biggest reasons <strong>Gateway API</strong> was introduced 🚀</p>
<h2>4. Gateway API</h2>
<p>So far, we explored:</p>
<ul>
<li><p><code>NodePort</code> → Simple but not production friendly</p>
</li>
<li><p><code>LoadBalancer</code> → Production ready but expensive</p>
</li>
<li><p><code>Ingress</code> → Cost-effective and production-ready</p>
</li>
</ul>
<p>At this point, you might be wondering:</p>
<blockquote>
<p>If Ingress already solves the problem, why do we even need Gateway API? 🤔</p>
</blockquote>
<p>Fair question 😄</p>
<h3>What is Gateway API?</h3>
<p><strong>Gateway API</strong> is the <strong>modern and more flexible way</strong> of managing traffic into Kubernetes clusters.</p>
<p>At a high level, it still follows a similar architecture as Ingress: under the hood, it still uses a small number of Load Balancers and routes traffic through them.</p>
<p>Then what changed? The answer is simple:</p>
<blockquote>
<p>Better separation of responsibilities and better delegation.</p>
</blockquote>
<p>And trust me this becomes a huge deal in real production environments 😄</p>
<h3>Why Was Gateway API Introduced?</h3>
<p>Ingress works. No doubt about that. But over time, teams realized it has some important limitations.</p>
<ul>
<li><p><strong>One Component Handles Too Many Responsibilities</strong></p>
<ul>
<li><p>Ingress Controllers usually handle: Load balancing, TLS termination, Rate limiting, Traffic filtering, Authentication, Routing</p>
</li>
<li><p>Basically One component doing many jobs.</p>
</li>
<li><p>This is manageable initially, but at scale it becomes harder to maintain and customize.</p>
</li>
</ul>
</li>
<li><p><strong>Limited Delegation Model (The Real Production Pain 😅)</strong></p>
<ul>
<li><p>Let’s understand this with a real-world example. Assume you are a <strong>DevOps Engineer</strong>. You:</p>
<ul>
<li><p>Deploy the Ingress Controller</p>
</li>
<li><p>Configure networking rules</p>
</li>
<li><p>Create Ingress resources for applications</p>
</li>
</ul>
</li>
<li><p>Now imagine a developer comes and says: <em>"Hey, I need to update my route."</em></p>
</li>
<li><p>For Example</p>
<ul>
<li><p><code>/payments → /payments-v2</code></p>
</li>
<li><p>(Or) <a href="http://api.company.com"><code>api.company.com</code></a> <code>→</code> <a href="http://payments.company.com"><code>payments.company.com</code></a></p>
</li>
</ul>
</li>
<li><p>Can the developer directly update the Ingress configuration? Usually… <strong>No</strong> 👀</p>
</li>
<li><p>Why ? Because Ingress is <strong>shared infrastructure</strong>.</p>
</li>
<li><p>If routing is <strong>path-based,</strong> You must ensure:</p>
<ul>
<li><p>No other application is already using the same path</p>
</li>
<li><p>Existing routes do not break</p>
</li>
</ul>
</li>
<li><p>If routing is host-based, You must ensure:</p>
<ul>
<li><p>The hostname is unique</p>
</li>
<li><p>TLS and DNS configurations remain correct</p>
</li>
</ul>
</li>
<li><p>And here comes the scary part 😅. A small mistake by one team can accidentally impact <strong>many other applications</strong>, because everyone is sharing the same Ingress Controller.</p>
</li>
<li><p>This creates: <code>Developer → Raise Request → DevOps Review → Cross Verification → Deployment</code></p>
</li>
<li><p>And suddenly even a small routing change becomes a ticket, approval process, and deployment delay 😅 . This is one of the biggest pain points Gateway API was designed to solve.</p>
</li>
</ul>
</li>
</ul>
<h3>How Gateway API Solves This ?</h3>
<p>Gateway API separates responsibilities into <strong>independent resources</strong>.</p>
<p>Instead of one big Ingress object handling everything, Gateway API breaks things into smaller, manageable components. The main components are:</p>
<ul>
<li><p><code>GatewayClass</code></p>
</li>
<li><p><code>Gateway</code></p>
</li>
<li><p><code>HTTPRoute</code></p>
</li>
</ul>
<p>Each component can be owned and managed by <strong>different teams</strong>. This separation is what makes Gateway API powerful.</p>
<h3>GatewayClass</h3>
<p>This defines: <strong>Which Gateway implementation/tool should be used</strong></p>
<p>Just like Ingress had multiple controllers (<code>NGINX</code>, <code>Traefik</code>, etc.), Gateway API also supports different implementations. Some popular options include:</p>
<ul>
<li><p>Cilium</p>
</li>
<li><p>Istio</p>
</li>
<li><p>Calico</p>
</li>
<li><p>Envoy Gateway</p>
</li>
<li><p>Kong</p>
</li>
</ul>
<p>Think of this as: <em><strong>"Which engine are we using under the hood?".</strong></em> For example: <em>We chose Cilium as our Gateway implementation.</em></p>
<p>You can technically have multiple GatewayClasses for different teams or business units. Although possible, keeping too many GatewayClasses is usually not considered best practice unless there is a strong reason.</p>
<p><strong>Ownership:</strong> This is usually managed by the <strong>Infrastructure Team</strong>. Because platform wide networking decisions are generally centralized.</p>
<h3>Gateway</h3>
<p>This is similar to the <strong>Ingress Controller layer</strong>. The <code>Gateway</code> defines:</p>
<ul>
<li><p>Which listeners to expose (<code>HTTP</code>, <code>HTTPS</code>)</p>
</li>
<li><p>Which ports to open</p>
</li>
<li><p>Which domains to allow</p>
</li>
<li><p>Which <code>GatewayClass</code> to use</p>
</li>
</ul>
<p>Think of this as: <strong>The actual traffic entry point into the cluster.</strong></p>
<p><strong>Example:</strong></p>
<p>You may have:</p>
<p><code>Public Gateway → Internet-facing → Strong TLS → Rate limiting</code></p>
<p>and</p>
<p><code>Internal Gateway → Internal traffic only → Relaxed restrictions</code></p>
<p><strong>Ownership:</strong> Usually managed by the <strong>Platform Team</strong> or <strong>DevOps Team</strong>. Because this still affects cluster wide networking behavior.</p>
<h3>HTTPRoute</h3>
<p>This is where things get interesting 😄</p>
<p><code>HTTPRoute</code> is the equivalent of the <strong>Ingress resource</strong>, but with one major improvement: <strong>It can be safely delegated to developers.</strong></p>
<p>Here developers define:</p>
<ul>
<li><p>Hostnames</p>
</li>
<li><p>Paths</p>
</li>
<li><p>Backend Services</p>
</li>
<li><p>Traffic rules</p>
</li>
</ul>
<p>And simply attach the route to an approved <code>Gateway</code>.</p>
<p>Example: <code>/payments → payment-service</code> . The developer can manage their own routing <strong>without touching shared infrastructure</strong>. And trust me… this is huge 🚀 Because now:</p>
<p><strong>Developers Can Move Independently:</strong> Developers no longer need to depend on DevOps teams for every small routing update.</p>
<p><em>Want to add a new endpoint?, Want to change a path?, Want to update service mapping?</em></p>
<p>They can manage it themselves through their own <code>HTTPRoute</code> resource.</p>
<p><strong>Reduced Blast Radius:</strong></p>
<p>But wait…</p>
<p><strong>How exactly does Gateway API reduce the blast radius? This will also</strong></p>
<p>Let’s understand this with a real world example 😄. This will also help you to understand how Developers Can Move Independently</p>
<p>In most organizations using traditional <strong>Ingress</strong>, teams commonly share host such as: <code>api.company.com</code> and use <strong>path-based routing</strong> to route traffic to different applications. Something like this:</p>
<p><code>/payments → payment-service</code></p>
<p><code>/users → user-service</code></p>
<p><code>/orders → order-service</code></p>
<p>Now here comes the problem 👀. In many cases, <strong>all these routes live inside the same shared Ingress resource file</strong>. That means if the Payments team wants to make a change, they are editing a file that also contains routes for: Users Service, Order Service etc.</p>
<p>Now imagine someone accidentally:</p>
<ul>
<li><p>Updates the wrong route</p>
</li>
<li><p>Creates a conflicting path</p>
</li>
<li><p>Adds <code>/orders</code> without realizing it already exists</p>
</li>
<li><p>Changes path priority incorrectly</p>
</li>
</ul>
<p>Suddenly: <code>Payments issue → Orders affected → Users affected → Unexpected production issue 😨</code></p>
<p>Why? Because everything is tightly coupled inside a <strong>shared Ingress configuration</strong>. This increases the <strong>blast radius</strong>. A small mistake from one team can potentially impact multiple services. And trust me this happens more often than people admit 😅</p>
<h3>How Gateway API Fixes This</h3>
<p>With <strong>Gateway API</strong>, teams are encouraged (and mostly forced by design) to create <strong>separate</strong> <code>HTTPRoute</code> <strong>resources for each application</strong>.</p>
<p>Even if everyone is still using: <code>api.company.com</code> with path-based routing, each application owns <strong>its own independent routing file</strong>. Example</p>
<p><code>payment-httproute.yaml</code></p>
<p><code>users-httproute.yaml</code></p>
<p><code>orders-httproute.yaml</code></p>
<p>Now if the Payments team makes a bad change they only touch <code>payment-httproute.yaml</code> if any issue only gets payment service gets affected. The <code>users</code> and <code>orders</code> routes continue working normally because their configurations are completely isolated. So now the blast radius becomes:</p>
<p><code>Ingress → One bad config can affect many apps 😨</code></p>
<p><code>Ingress → One bad config can affect many apps 😨 Gateway API → Mostly isolated to one app 😌</code></p>
<p>This is one of the easiest ways to remember why <strong>Gateway API improves both delegation and reliability</strong> in shared Kubernetes environments.</p>
<h3>Pros</h3>
<ul>
<li><p>Better separation of responsibilities</p>
</li>
<li><p>Clear ownership model across teams</p>
</li>
<li><p>Safer delegation to developers</p>
</li>
<li><p>Reduced blast radius during failures</p>
</li>
<li><p>More flexible and expressive routing rules</p>
</li>
<li><p>Standardized Kubernetes-native API</p>
</li>
<li><p>Supports advanced traffic management</p>
</li>
<li><p>Easier to scale for large organizations</p>
</li>
</ul>
<h3>Cons</h3>
<ul>
<li><p>More components to understand initially</p>
</li>
<li><p>Slight learning curve compared to Ingress</p>
</li>
<li><p>Ecosystem is still evolving in some areas</p>
</li>
<li><p>Not every controller supports all Gateway API features equally</p>
</li>
</ul>
<h2>Should Gateway API Replace Ingress?</h2>
<p>Not necessarily. Ingress is still heavily used and works perfectly fine for many organizations.</p>
<p>But if you are dealing with:</p>
<ul>
<li><p>Multiple teams</p>
</li>
<li><p>Shared clusters</p>
</li>
<li><p>Complex routing needs</p>
</li>
<li><p>Delegation challenges</p>
</li>
</ul>
<p>Gateway API becomes a much cleaner and scalable option.</p>
<h1>Wrapping Up: Choosing Your Front Door 🚪</h1>
<p>Exposing your applications to the outside world isn't a one size fits all decision. As we've seen, Kubernetes networking has evolved to solve real world operational headaches:</p>
<ul>
<li><p><strong>NodePort</strong> is your quick and dirty tool for local debugging and testing.</p>
</li>
<li><p><strong>LoadBalancer</strong> gives you robust, cloud native connectivity but comes with a hefty price tag at scale.</p>
</li>
<li><p><strong>Ingress</strong> solves the cost problem by putting multiple apps behind a single cloud load balancer, but it can introduce operational bottlenecks and shared configuration risks.</p>
</li>
<li><p><strong>Gateway API</strong> is the modern evolution, breaking down those bottlenecks by splitting responsibilities so infrastructure, platform, and development teams can move independently without stepping on each other's toes.</p>
</li>
</ul>
<p>If you are running a small project or a single app, Ingress or a simple LoadBalancer might be all you ever need. But if you are scaling across multiple teams in a shared cluster, adopting the Gateway API will save you from a lot of configuration conflicts and deployment delays down the road. 🚀</p>
<h2>What’s Next? 🛠️</h2>
<p>We’ve covered the high level architecture and the <em>why</em> behind each approach, but we’re just scratching the surface!</p>
<p>To really get deep into these tools, we need to look under the hood. In our upcoming posts, <strong>we will explore each of these options in deep, technical detail in separate blogs</strong>, including step by step implementation guides, YAML manifests, and production best practices.</p>
<p>Stay tuned for the next part, where we’ll roll up our sleeves and dive deep into <strong>each of these</strong> options! Until then, happy routing! ☕</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking : Part 2 : Guide to Kubernetes Service Type]]></title><description><![CDATA[In the previous blog, we explored the fundamentals of Kubernetes Services, where we discussed how Pods communicate with each other, the role Services play, and how the communication actually works beh]]></description><link>https://claybrainer.com/kubernetes-networking-part-2-guide-to-kubernetes-service-type</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-part-2-guide-to-kubernetes-service-type</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Kubernetes-networking]]></category><category><![CDATA[#kubernetesNetworking]]></category><category><![CDATA[Kubernetes Networking Deep Dive]]></category><category><![CDATA[#clusterip]]></category><category><![CDATA[cluster ip]]></category><category><![CDATA[Kubernetes nodeport]]></category><category><![CDATA[NodePort]]></category><category><![CDATA[Load Balancer]]></category><category><![CDATA[kubernetes-load-balancer]]></category><category><![CDATA[Headless Services ]]></category><category><![CDATA[headlessservice]]></category><category><![CDATA[k8s headless service]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 07 Jun 2026 07:18:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/4b09ccef-fbed-44a8-87a6-de630a82250a.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous blog, we explored the fundamentals of Kubernetes Services, where we discussed how Pods communicate with each other, the role Services play, and how the communication actually works behind the scenes. If you are new to this topic, I would highly recommend going through the <a href="https://claybrainer.com/kubernetes-fundamentals-high-level"><strong>Kubernetes Fundamentals</strong></a> and <a href="https://claybrainer.com/kubernetes-networking-part-1-the-fundamentals"><strong>Kubernetes Networking – Part 1</strong></a> blogs first, as they will help you better understand the concepts we will discuss here.</p>
<p>That being said, in this blog, we will dive deeper into the different types of Services Kubernetes provides and understand when and why to use them in real world scenarios.</p>
<h1>Kubernetes Services Types</h1>
<p>We already know the purpose of a Kubernetes Service it provides a <strong>stable DNS name</strong> and a <strong>stable IP address</strong>, allowing communication to happen seamlessly without interruption.</p>
<p>We also learned that Pods cannot have stable IPs because of their <strong>ephemeral nature</strong>. A Pod can be terminated, recreated, or rescheduled at any time, which means its IP address can change. This is exactly why we place a <strong>Service on top of a Pod or Deployment</strong> to provide a stable entry point (stable IP and DNS) so applications can continue communicating regardless of what happens at the Pod or Deployment level.</p>
<p>Now that we understand why Services are needed, let’s look at the different Service types Kubernetes provides.</p>
<p>Kubernetes offers <strong>five types of Services</strong>, and each of them is designed for a specific purpose and use case. At the time of writing, Kubernetes provides the following Service types:</p>
<ul>
<li><p><strong>ClusterIP (Default)</strong></p>
</li>
<li><p><strong>NodePort</strong></p>
</li>
<li><p><strong>Load Balancer</strong></p>
</li>
<li><p><strong>Headless Service</strong></p>
</li>
<li><p><strong>ExternalName</strong></p>
</li>
</ul>
<p>Each Service type solves a different networking problem. Some are meant for internal communication within the cluster, while others help expose applications externally.</p>
<p>In the upcoming sections, we will explore each Service type in detail, understand how they work behind the scenes, and discuss when to use them in real-world scenarios.</p>
<h2>Cluster IP</h2>
<p>This is the <strong>default Service type</strong> available in Kubernetes. If you do not explicitly specify a Service type, Kubernetes automatically creates it as a <strong>ClusterIP Service</strong>.</p>
<h3>What it is ?</h3>
<p>ClusterIP is a Service type that enables <strong>internal communication within the Kubernetes cluster</strong>. Instead of directly communicating with Pods using their changing Pod IPs, applications communicate through a <strong>stable Service IP and DNS name</strong>.</p>
<p>As we learned earlier, Pods are ephemeral in nature they can be recreated, restarted, or rescheduled at any time, which means their IP addresses may change. Kubernetes solves this problem by placing a <strong>ClusterIP Service</strong> in front of the Pods.</p>
<p>Behind the scenes, <strong>kube-proxy</strong> updates networking rules so traffic sent to the Service IP is automatically routed to the healthy backend Pods.</p>
<p>One important caveat here is that <strong>the application is not exposed to the public internet</strong>. Communication through a ClusterIP Service is restricted <strong>only within the cluster</strong>.</p>
<h3>When this is used?</h3>
<p>This is the <strong>most commonly used Service type</strong> in Kubernetes. In real world deployments, most applications run internally using <strong>ClusterIP Services</strong>.</p>
<p>But wait… if applications are internal, how do users access them?</p>
<p>This is where components like <strong>Ingress, Gateway API, Load Balancers, or NGINX</strong> come into the picture. Instead of exposing every application individually, organizations usually expose a <strong>common entry point</strong> that routes traffic internally to multiple ClusterIP Services.</p>
<p><strong>Why is it done this way?</strong> There are multiple reasons for this approach:</p>
<ul>
<li><p><strong>Cost optimization</strong> – Exposing every Service through a LoadBalancer can significantly increase infrastructure costs.</p>
</li>
<li><p><strong>Better traffic control</strong> – A central entry point gives teams more control over routing, authentication, rate limiting, and security.</p>
</li>
<li><p><strong>Avoid operational issues</strong> – Using NodePort for every application can lead to port management and conflict issues.</p>
</li>
</ul>
<p>Because of these reasons, most organizations keep applications internal using <strong>ClusterIP</strong> and expose only selected endpoints externally through <strong>Ingress Controllers, Gateway APIs, or Load Balancers</strong>.</p>
<h3>Try it yourself :</h3>
<p>You can follow the <a href="https://github.com/naveenkumarvr/kubernetes_learning/blob/main/networking/fundamentals/02-pod-and-service-communication.md">practical example here</a> to see how a <strong>ClusterIP Service</strong> works in action and understand how Pods communicate through a stable Service endpoint.</p>
<h2>Node Port</h2>
<p>This type of Service is mainly used for <strong>local testing, lab setups, or Proof of Concepts (PoCs)</strong>. It is <strong>not very common in production environments</strong>, although there are some edge cases where teams still use it.</p>
<p>NodePort exposes an application <strong>outside the Kubernetes cluster</strong> by opening a specific port on <strong>every node</strong> in the cluster.</p>
<h3>What it is ?</h3>
<p>When you create a Service with the <strong>NodePort</strong> type, Kubernetes exposes your application externally using a port on the node.</p>
<p>This means you can access the application using: <code>&lt;NodeIP&gt;:&lt;NodePort&gt;</code>. For example: <code>192.168.1.10:30080</code>.</p>
<p>By default, Kubernetes allocates a port from the <strong>30000–32767 range</strong>, although you can manually specify a port if needed.</p>
<p>Behind the scenes, Kubernetes creates a <strong>ClusterIP Service first</strong>, and then the NodePort Service extends it by exposing that application externally through the node.</p>
<h3>But wait… what if my Pods are running on different nodes?</h3>
<p>Good question 🙂</p>
<p>Imagine your application Pod is running only on <strong>Node 2</strong>, but you accidentally access the <strong>NodePort of Node 1</strong>. <em>Will it fail?</em> <em>No.</em></p>
<p>When Kubernetes creates a NodePort Service, the same port is opened on <strong>all nodes</strong> in the cluster. If traffic reaches a node where the Pod is not running, <strong>kube-proxy forwards the request internally to a healthy Pod running on another node</strong>.</p>
<p>For example:</p>
<pre><code class="language-plaintext">Node 1 → No Pod running
Node 2 → Pod running
Node 3 → No Pod running
</code></pre>
<p>If you access: <code>http://Node1IP:30080</code>.The request will still work because Kubernetes internally forwards it to the Pod running on <strong>Node 2</strong>. This is one of the reasons why NodePort feels simple to use during testing.</p>
<h3><strong>One important caveat:</strong></h3>
<p>Although NodePort exposes applications externally, it is usually <strong>not ideal for internet facing production applications</strong>.</p>
<p><strong>Why?</strong></p>
<ul>
<li><p>Ports are limited and need manual management.</p>
</li>
<li><p>You expose node level networking directly.</p>
</li>
<li><p>Security becomes harder to manage.</p>
</li>
<li><p>You need to know node IP addresses.</p>
</li>
<li><p>Scaling and routing become difficult compared to using Ingress or Load Balancers.</p>
</li>
</ul>
<h3>When this is used?</h3>
<p>NodePort is mostly used for:</p>
<ul>
<li><p><strong>Local testing</strong></p>
</li>
<li><p><strong>Learning Kubernetes</strong></p>
</li>
<li><p><strong>Lab environments</strong></p>
</li>
<li><p><strong>Proof of Concepts (PoCs)</strong></p>
</li>
<li><p><strong>Temporary external access during debugging</strong></p>
</li>
</ul>
<p>In production environments, teams usually prefer <strong>LoadBalancer Services</strong>, <strong>Ingress Controllers</strong>, or <strong>Gateway APIs</strong> instead of directly exposing applications through NodePort.</p>
<h3>Try it yourself :</h3>
<p>Let’s create a simple NodePort Service and see how it works.</p>
<p>Step 1: Create an NGINX Deployment</p>
<pre><code class="language-yaml">#Create a file called `nginx-deployment.yaml`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-app
  template:
    metadata:
      labels:
        app: nginx-app
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80

---
#Create a file called nginx-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx-app
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080
---

#Apply it using following command
kubectl apply -f nginx-nodeport.yaml

#Check the Service
kubectl get svc

#Check nodes
kubectl get nodes -o wide

#Now Access
http://&lt;NodeIP&gt;:30080

# Try different node IPs, even nodes where the Pod is not running.
</code></pre>
<p>You can inspect the Service: <code>kubectl describe svc nginx-nodeport</code></p>
<p>Check endpoints: <code>kubectl get endpoints</code></p>
<p>This helps you understand which Pods are backing the Service.</p>
<h2>Load Balancer</h2>
<p>This is one of the <strong>most commonly used Service types in production grade Kubernetes environments</strong> to expose applications to the outside world.</p>
<p>But wait… does that mean every application gets its own Load Balancer? <strong>Not really.</strong></p>
<p>As we discussed earlier, exposing every application using a <strong>LoadBalancer Service</strong> can become very expensive because each Load Balancer typically provisions a public IP and cloud resources.</p>
<p>You might be wondering: <em>Then how do companies expose applications externally in production?</em> Good question 🙂</p>
<p>In the real world, organizations usually expose only a few components such as <strong>Ingress Controllers</strong> (NGINX, Traefik, HAProxy, etc.) or <strong>Gateway APIs</strong> using a <strong>LoadBalancer Service</strong>.</p>
<p>Then, all application traffic is routed internally through these components to the actual backend services running as <strong>ClusterIP Services</strong>.</p>
<p>This approach helps organizations:</p>
<ul>
<li><p><strong>Reduce infrastructure cost</strong></p>
</li>
<li><p><strong>Centralize traffic management</strong></p>
</li>
<li><p><strong>Improve security</strong></p>
</li>
<li><p><strong>Manage routing in a scalable way</strong></p>
</li>
</ul>
<p>Instead of provisioning <strong>50 Load Balancers for 50 applications</strong>, companies usually use <strong>one or a few Load Balancers</strong> and route traffic internally.</p>
<h3>What it is ?</h3>
<p>A <strong>LoadBalancer Service</strong> exposes your Kubernetes application to the <strong>public internet</strong>.</p>
<p>When you create a Service with the type <strong>LoadBalancer</strong>, Kubernetes works with the underlying cloud provider (AWS, Azure, GCP, etc.) to provision an external Load Balancer and assign a <strong>public IP address</strong> (or DNS endpoint).</p>
<p>This public endpoint becomes the entry point through which users can access your application. For example: <code>http://&lt;External-IP&gt;</code> or <code>http://my-app.company.com</code></p>
<p>Behind the scenes, Kubernetes creates:</p>
<p><code>LoadBalancer → NodePort → ClusterIP → Pod</code></p>
<p><strong>Did you notice it ?</strong> Yes, you read that right 🙂. A <strong>LoadBalancer Service internally uses NodePort</strong>.</p>
<p>Here is how the traffic flow works:</p>
<ol>
<li><p>The external Load Balancer receives incoming traffic.</p>
</li>
<li><p>It forwards traffic to the <strong>NodePort</strong> opened on cluster nodes.</p>
</li>
<li><p>kube-proxy routes the request to the appropriate <strong>ClusterIP Service</strong>.</p>
</li>
<li><p>Finally, the traffic reaches a healthy backend <strong>Pod</strong>.</p>
</li>
</ol>
<p>This is why when you create a LoadBalancer Service, you may notice a <strong>NodePort also getting created automatically</strong>.</p>
<p>One important thing to remember. A <strong>LoadBalancer Service works differently depending on where Kubernetes is running</strong>. In cloud managed Kubernetes environments such as: AWS EKS, Azure AKS, Google GKE</p>
<h3>When is this used?</h3>
<p>Use a <strong>LoadBalancer Service</strong> whenever you want to expose an application externally.</p>
<p>Typical use cases include:</p>
<ul>
<li><p>Public facing web applications</p>
</li>
<li><p>APIs exposed to external consumers</p>
</li>
<li><p>Applications requiring external access</p>
</li>
</ul>
<p>However, in production, teams usually avoid assigning a separate Load Balancer to every application. Instead, they expose <strong>Ingress Controllers or Gateway APIs</strong> through a LoadBalancer and route traffic internally to backend services.</p>
<h3>Try it yourself</h3>
<p>You can follow the practical guide linked below to create a <a href="https://github.com/naveenkumarvr/kubernetes_learning/blob/main/networking/fundamentals/03-LoadBalancerService-practice.md"><strong>LoadBalancer Service</strong></a>, understand how it works internally, and see how Kubernetes exposes applications externally.</p>
<h2>Headless Service</h2>
<p>This is one of the <strong>most misunderstood Service types</strong> in Kubernetes. It is called Headless service because it <strong>doesn't have a</strong> <strong>stable Service IP</strong> which we used to have in all other services. At first glance, you may wonder:</p>
<blockquote>
<p>Wait… why would anyone want a Service <strong>without a Service IP</strong>?</p>
</blockquote>
<p>Good question 🙂</p>
<p>A <strong>Headless Service</strong> is mainly used when applications need to directly communicate with <strong>individual Pods</strong>, instead of a load-balanced Service endpoint.</p>
<h3>What is it?</h3>
<p>In a normal Kubernetes Service (ClusterIP), traffic flows like this: <code>Application → Service IP → One of the Pods</code></p>
<p>Kubernetes automatically load balances requests across Pods. But in some cases, applications want to know:</p>
<blockquote>
<p>Which exact Pod am I talking to?</p>
</blockquote>
<p>For such scenarios, Kubernetes provides <strong>Headless Services</strong>. A Headless Service is created by setting: <code>clusterIP: None</code></p>
<p>This tells Kubernetes: <strong>“Do not assign a Cluster IP. Give me direct Pod-level access.”</strong></p>
<p>Instead of returning a single Service IP, Kubernetes DNS returns the <strong>individual Pod IPs</strong> behind the Service. For example:</p>
<p>Instead of:</p>
<pre><code class="language-plaintext">my-app.default.svc.cluster.local → 10.96.10.20
</code></pre>
<p>You may get:</p>
<pre><code class="language-plaintext">pod-1.my-app.default.svc.cluster.local
pod-2.my-app.default.svc.cluster.local
pod-3.my-app.default.svc.cluster.local
</code></pre>
<h3>Why do we need this?</h3>
<p>Let’s understand this with a simple <strong>Redis example</strong>. Assume you are running Redis in Kubernetes with:</p>
<pre><code class="language-plaintext">redis-0 → Primary (Leader)
redis-1 → Replica
redis-2 → Replica
</code></pre>
<p>Now imagine you expose Redis using a normal <strong>ClusterIP Service</strong>.</p>
<p>What happens? A normal Service will <strong>load balance traffic randomly</strong> across all Redis Pods. So your application may connect like this:</p>
<pre><code class="language-plaintext">Request 1 → redis-0 (Primary)
Request 2 → redis-2 (Replica)
Request 3 → redis-1 (Replica)
</code></pre>
<p>This becomes a problem.</p>
<p>Why? Because in Redis:</p>
<ul>
<li><p><strong>Writes must go to the Primary node</strong></p>
</li>
<li><p><strong>Reads may go to Replicas</strong></p>
</li>
<li><p>Replicas synchronize data from the Primary</p>
</li>
</ul>
<p>If Kubernetes randomly sends traffic to any Redis Pod through a normal Service, your write requests may accidentally land on a <strong>Replica</strong>, which is not what we want.</p>
<p>So how do we solve this?</p>
<p>This is where <strong>Headless Services</strong> become useful.</p>
<p>Instead of giving one load-balanced Service IP, Kubernetes exposes the <strong>individual Pod DNS records</strong>.</p>
<p>For example:</p>
<pre><code class="language-plaintext">redis-0.redis.default.svc.cluster.local
redis-1.redis.default.svc.cluster.local
redis-2.redis.default.svc.cluster.local
</code></pre>
<p>Now the Redis cluster (or your application) can directly talk to the <strong>specific Pod it wants</strong>. So here the app or the Redis Cluster will decide which one to use rather Kubernetes deciding for you.</p>
<p>For example:</p>
<ul>
<li><p>Write traffic → <code>redis-0</code> (Primary)</p>
</li>
<li><p>Read traffic → <code>redis-1</code> or <code>redis-2</code> (Replicas)</p>
</li>
</ul>
<p>This gives applications <strong>full control</strong> over which Pod they communicate with.</p>
<p>And this is exactly why <strong>Headless Services are commonly used with StatefulSets and databases</strong>.</p>
<p>In short:</p>
<blockquote>
<p>If your application needs <strong>Pod level awareness</strong>, a normal Service may not be enough and that is where Headless Services shine 🙂</p>
</blockquote>
<h3>When is this used?</h3>
<p>Headless Services are commonly used with:</p>
<ul>
<li><p><strong>StatefulSets</strong></p>
</li>
<li><p>Distributed databases</p>
</li>
<li><p>Clustered applications</p>
</li>
<li><p>Messaging systems</p>
</li>
<li><p>Peer-to-peer communication between Pods</p>
</li>
</ul>
<p>In most regular stateless applications, you probably <strong>do not need Headless Services</strong>. A standard <strong>ClusterIP Service</strong> is usually enough.</p>
<h3>Try it yourself</h3>
<p>You can follow the practical guide to create a <a href="https://github.com/naveenkumarvr/kubernetes_learning/blob/main/networking/fundamentals/04-headless-service.md"><strong>Headless Service</strong>,</a> understand how it works.</p>
<h2>ExternalName</h2>
<p>This is probably the <strong>least commonly used Service type</strong> in Kubernetes, but it can still be very useful in certain situations. Unlike other Service types, <strong>ExternalName does not create a ClusterIP, NodePort, or LoadBalancer</strong>.</p>
<p>Wait… then what exactly does it do? An <strong>ExternalName Service</strong> simply acts as a <strong>DNS alias (mapping)</strong> inside Kubernetes. Instead of routing traffic to Pods inside the cluster, it redirects requests to an <strong>external DNS name</strong>.</p>
<h3>What is it?</h3>
<p>Imagine your application running inside Kubernetes needs to communicate with an <strong>external database</strong>, API, or third-party service.</p>
<p>Normally, your application would directly call: <code>mydb.company.com</code></p>
<p>But what if the endpoint changes later? You would need to update configurations in multiple places.</p>
<p>This is where <strong>ExternalName</strong> becomes useful. You can create a Kubernetes Service that maps:</p>
<pre><code class="language-plaintext">database-service.default.svc.cluster.local
</code></pre>
<p>to:</p>
<pre><code class="language-plaintext">mydb.company.com
</code></pre>
<p>Now your application continues using the Kubernetes Service name, while Kubernetes redirects the request to the external DNS.</p>
<p>Behind the scenes, Kubernetes simply returns a <strong>CNAME DNS record</strong>. There is <strong>no proxying</strong>, <strong>no kube-proxy rules</strong>, and <strong>no Service IP</strong> involved.</p>
<h3>Example</h3>
<p>Suppose your payment service db is running outside Kubernetes: <code>mydb.abc123.us-east-1.rds.amazonaws.com</code>. Instead of hardcoding it in multiple applications, create an ExternalName Service:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: payment-service-db
spec:
  type: ExternalName
  externalName: mydb.abc123.us-east-1.rds.amazonaws.com
</code></pre>
<p>Now applications inside Kubernetes can simply access: <code>payment-service-db.default.svc.cluster.local</code>. Kubernetes will automatically redirect traffic to: <code>mydb.abc123.us-east-1.rds.amazonaws.com</code></p>
<h3>When is this used?</h3>
<p>ExternalName is commonly used when:</p>
<ul>
<li><p>Connecting Kubernetes apps to external databases</p>
</li>
<li><p>Connecting to third-party APIs</p>
</li>
<li><p>Migrating workloads gradually to Kubernetes</p>
</li>
<li><p>Avoiding hardcoded external URLs in applications</p>
</li>
<li><p>Creating consistent service naming conventions</p>
</li>
</ul>
<p><strong>One important caveat:</strong></p>
<p>Since this only works using <strong>DNS mapping</strong>, Kubernetes <strong>does not perform load balancing or health checks</strong>. If the external service is unavailable, Kubernetes cannot help much because the traffic is going directly to the external endpoint.</p>
<p>So think of this more like a <strong>DNS shortcut</strong> rather than a traditional Kubernetes Service.</p>
<p>You might now wonder: <em>If this is useful, why do many teams not use it often?</em></p>
<p>Good question 🙂 In real-world production environments, many organizations prefer using:</p>
<ul>
<li><p>Environment variables</p>
</li>
<li><p>Centralized configuration management</p>
</li>
<li><p>Service discovery tools</p>
</li>
<li><p>API gateways or service meshes</p>
</li>
</ul>
<p>instead of relying on <strong>ExternalName</strong>. Why?</p>
<p>Because ExternalName has a few limitations:</p>
<ul>
<li><p><strong>No traffic control</strong> – Kubernetes cannot route or balance traffic.</p>
</li>
<li><p><strong>No observability</strong> – Monitoring and tracing become harder.</p>
</li>
<li><p><strong>No failover mechanism</strong> – If the external endpoint fails, Kubernetes cannot automatically redirect traffic.</p>
</li>
<li><p><strong>DNS dependency</strong> – Everything depends on proper DNS resolution.</p>
</li>
</ul>
<p>Also, many modern architectures already manage external endpoints through <strong>configuration management systems</strong>, making ExternalName less necessary.</p>
<h1>Conclusion</h1>
<p>That brings us to the end of this blog. In this blog, we explored the <strong>different types of Kubernetes Services</strong> and understood why each Service type exists and what problem it solves.</p>
<p>We started with <strong>ClusterIP</strong>, the default Service type used for internal communication inside the cluster. Then we looked at <strong>NodePort</strong>, which helps expose applications externally for testing and lab environments. After that, we explored <strong>LoadBalancer</strong>, one of the most common approaches used in production to expose applications to the outside world.</p>
<p>We also discussed <strong>ExternalName</strong>, which acts more like a DNS mapping for external dependencies, and finally <strong>Headless Services</strong>, which are useful when applications need direct Pod-level communication instead of load balancing.</p>
<p>One important thing to remember is:</p>
<blockquote>
<p>There is <strong>no “best” Service type</strong> in Kubernetes.</p>
</blockquote>
<p>The right Service depends entirely on your <strong>use case, traffic pattern, security requirements, and architecture design</strong>.</p>
<p>For example:</p>
<ul>
<li><p>Need internal communication? → <strong>ClusterIP</strong></p>
</li>
<li><p>Need quick testing access? → <strong>NodePort</strong></p>
</li>
<li><p>Need external exposure? → <strong>LoadBalancer</strong></p>
</li>
<li><p>Need external DNS mapping? → <strong>ExternalName</strong></p>
</li>
<li><p>Need Pod-level discovery? → <strong>Headless Service</strong></p>
</li>
</ul>
<p>Also, in real world production environments, you will rarely see applications directly exposed using NodePort or individual LoadBalancers. Most organizations combine these Service types with <strong>Ingress Controllers</strong> or <strong>Gateway APIs</strong> to manage traffic in a more scalable and cost-effective way.</p>
<p>Now that we understand how Services work and the different ways applications are exposed, in the upcoming blogs we will dive deeper into <strong>Ingress, Gateway API, kube-proxy, and Kubernetes traffic flow behind the scenes</strong>.</p>
<p>Until then, try out the practical examples, break things, experiment, and most importantly learn by doing 🙂</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking : Part -1 : The Fundamentals ]]></title><description><![CDATA[We have covered the basics of how Kubernetes works at high level in our previous blog if you are new to kubernetes you can check this out here as this will add as a stepping stone for this Kubernetes ]]></description><link>https://claybrainer.com/kubernetes-networking-part-1-the-fundamentals</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-part-1-the-fundamentals</guid><category><![CDATA[Kubernetes-networking]]></category><category><![CDATA[#kubernetesNetworking]]></category><category><![CDATA[Kubernetes Networking Deep Dive]]></category><category><![CDATA[Networking in kubernetes]]></category><category><![CDATA[k8s fundamentals]]></category><category><![CDATA[kubernetes cni]]></category><category><![CDATA[Pod to Pod communication]]></category><category><![CDATA[kubernetes-services]]></category><category><![CDATA[KubernetesServices]]></category><category><![CDATA[kube-proxy]]></category><category><![CDATA[#endpointslices]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 31 May 2026 09:43:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/142ab2df-52b9-4220-92a2-1b0e14ab757d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We have covered the basics of how Kubernetes works at high level in our previous blog if you are new to kubernetes you can check this out <a href="https://claybrainer.com/kubernetes-fundamentals-high-level">here</a> as this will add as a stepping stone for this Kubernetes Networking Blog series.</p>
<p>In this series, we will dive deep into Kubernetes networking such as: how Pods communicate with each other, why Services are needed, the different types of Services, and much more.</p>
<p>So, buckle up: things are about to get interesting. Let’s get started 🚀</p>
<h1>Phase 1: How Do Pods Communicate with Each Other?</h1>
<p>From our previous blog, we learned that a Pod is the smallest deployable unit in Kubernetes. A Pod can have one or more containers, and this is where our application actually runs.</p>
<p>Now, if our application is running inside a Pod, we need some way to access it, right?</p>
<p>This is where IP addresses come into the picture. Every Pod in Kubernetes gets its own unique IP address. And here comes one of the coolest things about Kubernetes networking <strong>Pods can communicate with other Pods in the cluster directly. No NAT. No complex routing setup. No communication barriers (by default).</strong> This concept is called the <strong>Flat Network Model</strong>.</p>
<h2>Why is it called a Flat Network Model?</h2>
<p>We call it a flat network because:</p>
<ul>
<li><p>Every Pod gets a <strong>unique IP address</strong></p>
</li>
<li><p>Pods can communicate with each other <strong>across nodes</strong></p>
</li>
<li><p>No need for <strong>port mapping between Pods</strong></p>
</li>
<li><p>Pods can directly reach other Pods without an additional networking layer</p>
</li>
</ul>
<blockquote>
<p>Mental model</p>
<p><code>Pod A (10.244.1.2) → Pod B (10.244.2.5)</code></p>
<p>👉 Works directly even if on different nodes</p>
</blockquote>
<h3>🔥 Why Did Kubernetes Choose This Networking Model?</h3>
<p>Before we understand <em>why</em> Kubernetes chose this approach, let’s first see how networking usually worked in traditional container environments.</p>
<p><strong>Traditional Approach:</strong> <code>Container → NAT → Host → NAT → Another container</code>. This adds extra networking layers, port mappings, and sometimes additional complexity.</p>
<p><strong>Kubernetes Approach:</strong> <code>Pod → Pod (direct)</code>. No manual port mapping. No complicated networking setup between Pods. Kubernetes networking is designed so Pods can directly reach each other using their IP addresses.</p>
<p>✅ <strong>Benefits of This Approach</strong></p>
<ul>
<li><p><strong>Simpler service discovery</strong> - Pods and Services can communicate more predictably inside the cluster.</p>
</li>
<li><p><strong>No port conflict -</strong> Since every Pod gets its own IP address, multiple applications can use the same port without conflicts.</p>
</li>
<li><p><strong>Cleaner Architecture -</strong> Networking becomes easier to reason about, making application communication more straightforward.</p>
</li>
</ul>
<p>This is one of the key reasons Kubernetes is designed around a <strong>direct Pod-to-Pod communication model</strong> to make distributed systems easier to build and operate.</p>
<h2>🔌 How Pod Networking Actually Works</h2>
<p>So far, we learned that every Pod gets its own IP address and can communicate with other Pods in the cluster. Sounds cool, right?</p>
<p>But have you ever wondered <strong>what actually happens behind the scenes?</strong> How are these Pods connected? Who manages all this networking magic? Let’s break it down.</p>
<h3>🛠️ The Behind-the-Scenes Magic</h3>
<ul>
<li><p><strong>Every Pod Gets Its Own Network Namespace</strong></p>
<ul>
<li><p>Every pod gets: Its <strong>own network namespace</strong> and Its <strong>own unique IP address?</strong></p>
</li>
<li><p><strong>Network Namespace ?</strong> Think of a network namespace as a mini network environment for a Pod. It gets its own network interfaces, routing table, and network stack, isolated from other Pods. This is the reason why each Pod behaves almost like a separate machine in the cluster.</p>
</li>
</ul>
</li>
<li><p><strong>How Are Pods Connected?</strong></p>
<ul>
<li><p>Now comes the interesting part how do Pods actually talk to each other?</p>
</li>
<li><p>Pods are typically connected using something called <strong>veth pairs (Virtual Ethernet pairs)</strong>.</p>
</li>
<li><p>You can think of a veth pair like a virtual network cable: One end connects to the Pod and the other end connects to the host network.</p>
</li>
<li><p>From there, traffic is handled using mechanisms such as <strong>Linux bridges, routing, overlay networks, or eBPF</strong>, depending on the networking solution being used. Don’t worry if this sounds complicated right now we will simplify this step by step in upcoming blogs 😄</p>
</li>
</ul>
</li>
<li><p><strong>Who Manages All of This?</strong></p>
<ul>
<li><p>This is where the real rockstar enters the stage the <strong>CNI Plugin (Container Network Interface)</strong> 🎸</p>
</li>
<li><p>The CNI plugin is responsible for:</p>
<ul>
<li><p>Assigning an <strong>IP address to Pods</strong></p>
</li>
<li><p>Setting up the <strong>network interfaces</strong></p>
</li>
<li><p>Creating required <strong>routing/network rules</strong></p>
</li>
<li><p>Handling <strong>Pod-to-Pod communication across nodes</strong></p>
</li>
</ul>
</li>
<li><p>In short, without a CNI plugin, Kubernetes networking simply would not work.</p>
</li>
<li><p><strong>👀 Who Are These CNI Plugins?</strong> You might have heard names like: <strong>Flannel, Calico, Cilium.</strong> These are popular CNI plugins used in Kubernetes for networking.</p>
</li>
<li><p>Different companies choose different networking solutions based on their needs. Some prefer simplicity, while others need advanced networking, security policies, or high performance.</p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>Curious to know what CNI plugin your Kubernetes cluster is using? Run the below command:</p>
<p><code>kubectl get pods -n kube-system</code></p>
<p>Look for Pods with names related to networking. You might notice something familiar:</p>
<ul>
<li><p><code>calico-*</code> → Using <strong>Calico</strong></p>
</li>
<li><p><code>cilium-*</code> → Using <strong>Cilium</strong></p>
</li>
<li><p><code>kube-flannel-*</code> → Using <strong>Flannel</strong></p>
</li>
<li><p><code>aws-node</code> → Usually <strong>AWS VPC CNI</strong> (common in EKS)</p>
</li>
</ul>
</blockquote>
<p>For example, if you see Pods like <code>cilium-xyz123</code>, congratulations your cluster is most likely using <strong>Cilium</strong> 😄. Here is my example</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/f32d2534-7822-46d3-b583-00e7b13ff729.png" alt="" style="display:block;margin:0 auto" />

<h3>🧪 Want to Try It Yourself?</h3>
<p>If you want to do a hands on practical and see <strong>how Pod-to-Pod communication actually works</strong>, follow the <a href="https://github.com/naveenkumarvr/kubernetes_learning/blob/main/networking/fundamentals/01-pod-to-pod-communication.md">document</a> to test Pod-to-Pod communication in Kubernetes.</p>
<h2>The BIG problem this creates</h2>
<p>We learned in our previous blog that Pods are <strong>ephemeral</strong> in nature.</p>
<p><strong>Ephemeral?</strong> This means Pods are temporary if a Pod crashes, gets deleted, or is rescheduled, Kubernetes creates a new one (usually through controllers like Deployments).</p>
<p>But here’s the catch when a new Pod gets created, its <strong>IP address changes</strong>. Now imagine the headache this creates 😅</p>
<p>If Pod IPs keep changing constantly, how can other Pods reliably communicate with them? Tracking Pod IP addresses manually would be a tedious and messy job.</p>
<p>This is exactly where <strong>Kubernetes Services</strong> come to the rescue. Instead of talking directly to constantly changing Pod IPs, Services provide a <strong>stable way to access Pods</strong>, even when Pods come and go behind the scenes.</p>
<h1>Phase 2: Services — The Backbone of Kubernetes Networking</h1>
<p>We understood one important problem with Pods: they are <strong>ephemeral</strong>.</p>
<p>Luckily, Kubernetes has a smart solution for this problem <strong>Services</strong>. But the big question is: <strong>How do Services actually solve this issue?</strong></p>
<h2>How Services Resolve This Problem 🤔 ?</h2>
<p>A <strong>Kubernetes Service</strong> provides:</p>
<ul>
<li><p>A <strong>Stable Virtual IP</strong></p>
</li>
<li><p>A <strong>Stable DNS name</strong></p>
</li>
</ul>
<p>This stable identity dynamically maps to the Pods running your application. Think of it like this:</p>
<ul>
<li><p>Instead of applications directly talking to Pod IPs (which keep changing), they talk to a <strong>Service</strong>.</p>
</li>
<li><p>The Service acts like a stable entry point and forwards requests to the correct Pods behind the scenes.</p>
</li>
</ul>
<p>So, your setup looks something like this: <code>pod-b → Service → pod-a (new Pod)</code></p>
<p>Every Pod created by your application (<em>usually managed by a Deployment</em>) gets connected to the Service through labels and selectors.</p>
<p>The important part? The <strong>Service IP and DNS name stay stable</strong>, even if Pods are destroyed and recreated. We will learn <em>how Kubernetes actually does this magic behind the scenes</em> shortly 😄</p>
<h3>Why Is This Powerful?</h3>
<p>No matter how many times your Pods restart… No matter what new IP addresses your Pods get… Your application always talks to the <strong>Service</strong>, not directly to the Pods. The Service takes care of routing your request to the correct Pod and returning the response.</p>
<h1>✨ How Kubernetes Services Actually Do the Magic</h1>
<p>Now comes the interesting part <strong>how does a Kubernetes Service actually take your request to the right Pod?</strong> Until now, we learned that Services provide a stable IP and DNS name. But what’s happening behind the scenes that makes this possible? Let’s break it down step by step.</p>
<h3>The Basics</h3>
<p>We already know what a <strong>Deployment</strong> is. A Deployment manages multiple replicas of your application and takes care of things like: Auto healing (self-recovery), Rolling Updates and Scaling</p>
<p>Let’s say you deploy your application with <strong>3 replicas</strong>. What happens next?</p>
<p><strong>The Flow Behind the Scenes</strong></p>
<p><code>API Server → ETCD → Controller Manager → Scheduler → Kubelet → Container Runtime + CNI</code></p>
<p>Let’s understand this one by one:</p>
<p><strong>You create a Deployment:</strong> You apply your Deployment manifest.</p>
<ul>
<li><p><strong>API Server updates ETCD:</strong> The API Server stores the desired state of the Deployment inside <strong>ETCD</strong> (Kubernetes' database).</p>
</li>
<li><p><strong>Controller Manager notices the Deployment:</strong> The Controller Manager sees that 3 replicas are required and starts creating them.</p>
</li>
<li><p><strong>Scheduler assigns Nodes:</strong> The Scheduler notices new Pods are created but not assigned to any Node yet, so it decides where each Pod should run.</p>
</li>
<li><p><strong>Kubelet starts Pod creation:</strong> The <strong>Kubelet</strong> running on each Node notices that a Pod has been assigned to it and starts the Pod creation process.</p>
</li>
<li><p><strong>Container Runtime creates containers:</strong> The container runtime (like containerd or CRI-O) creates the containers.</p>
</li>
<li><p><strong>CNI Plugin joins the party 🎉:</strong> The CNI plugin: Assigns IP addresses to Pods, Configures networking, Creates routes required for Pod communication</p>
</li>
</ul>
<p>Parallely Services are created as per the manifest. And now… <strong>this is where the real magic starts</strong> 👀 Enter the <strong>EndpointSlice Controller</strong></p>
<h3>EndpointSlice Controller</h3>
<p>Kubernetes has something called the <strong>EndpointSlice Controller</strong>. Its job is to continuously watch for newly created Services. When a Service is created, the controller:</p>
<ul>
<li><p>Reads the <strong>selector labels</strong> from the Service definition</p>
</li>
<li><p>Finds Pods with matching labels in the same namespace</p>
</li>
<li><p>Collects the Pod IP addresses</p>
</li>
<li><p>Creates an <strong>EndpointSlice</strong> object</p>
</li>
</ul>
<p>Think of an EndpointSlice as a <strong>list of healthy Pod IPs behind a Service</strong>. Pretty cool, right?</p>
<p><strong>But Wait… It Doesn't Add Every Pod Immediately.</strong> The EndpointSlice Controller is smart. It does <strong>not blindly add Pod IPs</strong>. Instead, it checks whether the Pod is actually:</p>
<ul>
<li><p>Running</p>
</li>
<li><p>Healthy</p>
</li>
<li><p>Ready to accept traffic</p>
</li>
</ul>
<p>If you configure a <strong>Readiness Probe</strong>, Kubernetes waits for the Pod to become <strong>Ready = True</strong> before adding that Pod IP to the EndpointSlice. This prevents traffic from going to unhealthy Pods.</p>
<p>What Happens Next ?</p>
<h3>The Kube-Proxy Show</h3>
<p>Now <strong>kube-proxy</strong> takes over. kube-proxy watches these EndpointSlices and creates networking rules (usually using <strong>iptables</strong> or <strong>IPVS</strong>, depending on configuration). These rules make sure traffic sent to the Service gets forwarded to one of the healthy Pod IPs behind it.</p>
<p>In simple words: <code>Service → kube-proxy → EndpointSlice → Healthy Pod</code></p>
<blockquote>
<p>A Small but Important Detail: Whenever you create a Service, Kubernetes automatically creates an <strong>EndpointSlice</strong> for it.</p>
<p>You can actually see them using: <code>kubectl get endpointslices</code></p>
<p>You’ll notice names similar to your Service name with some random suffix added.</p>
</blockquote>
<h3>What If a Pod Goes Down?</h3>
<p>Imagine one Pod crashes unexpectedly. No worries — Kubernetes has your back 😄</p>
<p>The EndpointSlice Controller notices that the Pod is no longer healthy and removes that Pod IP from the EndpointSlice. This means traffic will stop going to the failed Pod.</p>
<p>When a healthy replacement Pod comes up, Kubernetes automatically adds the new Pod IP back.</p>
<h3>So, What’s the Final Magic?</h3>
<p>When your application receives a request:</p>
<ol>
<li><p>The request first hits the <strong>Service</strong></p>
</li>
<li><p>kube-proxy checks the available healthy endpoints</p>
</li>
<li><p>Traffic gets routed to one of the healthy Pod IPs</p>
</li>
<li><p>Your Pod processes the request and sends the response back</p>
</li>
</ol>
<p>And the best part?</p>
<p>You never have to care about changing Pod IPs.</p>
<p>Kubernetes handles all the heavy lifting behind the scenes 🚀</p>
<h3>🧪 Want to Try It Yourself?</h3>
<p>If you want to do a hands on practical and see <strong>how Pod and Service actually works</strong>, follow the <a href="https://github.com/naveenkumarvr/kubernetes_learning/blob/main/networking/fundamentals/02-pod-and-service-communication.md">document</a> to test Pod and Service communication in Kubernetes.</p>
<p>Trust me trying it practically makes Kubernetes networking much easier to understand than just reading theory 😄</p>
<h1>🛠️ Troubleshooting Communication Issues</h1>
<p>Let’s see some <strong>high-level troubleshooting steps</strong> to debug Kubernetes Service related issues.</p>
<blockquote>
<p><strong>Note:</strong> These steps only cover troubleshooting from the <strong>Service layer level</strong>.</p>
</blockquote>
<h2>1. Check if the Service Exists</h2>
<p><code>kubectl get svc</code></p>
<p>Make sure:</p>
<ul>
<li><p>The Service exists</p>
</li>
<li><p>The correct port is exposed</p>
</li>
<li><p>The ClusterIP is assigned</p>
</li>
</ul>
<h2>2. Check Service Details</h2>
<p><code>kubectl describe svc</code></p>
<p>Look for:</p>
<ul>
<li><p>Correct selector labels</p>
</li>
<li><p>Correct targetPort</p>
</li>
<li><p>Correct exposed port</p>
</li>
</ul>
<p>A label mismatch is one of the most common issues beginners face.</p>
<h2>3. Check if Pods Are Running</h2>
<p><code>kubectl get pods -o wide</code></p>
<p>Verify:</p>
<ul>
<li><p>Pods are in Running state</p>
</li>
<li><p>Pods are READY</p>
</li>
<li><p>Pod IPs are assigned</p>
</li>
</ul>
<h2>4. Check EndpointSlices (Very Important 👀)</h2>
<p><code>kubectl get endpointslices</code></p>
<p><code>kubectl describe endpointslice</code></p>
<p>This is where the real truth lives 😄 Check whether Pod IPs are actually present.</p>
<p>If you don’t see Pod IPs here, Kubernetes has no healthy backend to route traffic to.</p>
<h2>5. Check Pod Labels</h2>
<p><code>kubectl get pods --show-labels</code></p>
<p>Sometimes Pods exist, but labels don’t match the Service selector.</p>
<p>No matching labels = No endpoints.</p>
<h2>6. Check Readiness Probe Status</h2>
<p><code>kubectl describe pod</code></p>
<p>Look for:</p>
<ul>
<li><p>Ready: True</p>
</li>
<li><p>Probe failures</p>
</li>
<li><p>Events section</p>
</li>
</ul>
<p>If readiness checks fail, the Pod will not be added to EndpointSlice, meaning Services won’t send traffic to it.</p>
<h2>7. Test Connectivity from Another Pod</h2>
<pre><code class="language-plaintext">kubectl exec -it -- sh

# Then test:

curl http://&lt;service-name&gt;

#or

nslookup &lt;service-name&gt;
</code></pre>
<p>This helps verify DNS and Service connectivity. Most Kubernetes networking issues can be debugged by checking these few things:</p>
<p><code>Service → Labels → Pods → EndpointSlice → kube-proxy → Connectivity</code></p>
<h1>🎯 Wrapping Up — What We Learned</h1>
<p>And there you go we just uncovered the fundamentals of <strong>Kubernetes Networking</strong> 🚀</p>
<p>We started with understanding <strong>how Pods communicate with each other</strong>, why Kubernetes follows a <strong>Flat Network Model</strong>, and how <strong>CNI plugins</strong> quietly handle all the networking magic behind the scenes.</p>
<p>Then we explored the <strong>big problem with Pods</strong> they are <strong>ephemeral</strong>, meaning their IPs keep changing. This led us to one of the most important building blocks in Kubernetes networking: <strong>Services</strong>.</p>
<p>We learned how Services provide:</p>
<ul>
<li><p>A <strong>stable virtual IP</strong></p>
</li>
<li><p>A <strong>stable DNS name</strong></p>
</li>
<li><p>A reliable way for applications to communicate without worrying about changing Pod IPs</p>
</li>
</ul>
<p>And finally, we went under the hood to understand the real magic:</p>
<p><strong>Service → EndpointSlice → kube-proxy → Healthy Pod</strong></p>
<p>Now we know how Kubernetes Services actually route traffic to the right Pods behind the scenes 😄</p>
<h2>⏭️ What’s Next?</h2>
<p>So far, we focused on the fundamentals and understood <strong>why Services exist and how they work behind the scenes</strong>. But Kubernetes Services come in different flavors 😄</p>
<p>In the <strong>next blog</strong>, we will deep dive into the <strong>Types of Kubernetes Services.</strong> Until then, happy learning and keep experimenting 🚀</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Fundamentals: High Level]]></title><description><![CDATA[Let’s understand some basic Kubernetes concepts. Trust me if the fundamentals are clear, becomes much easier to understand other concepts.
Kubernetes Basics:
So, what exactly is Kubernetes? — Kubernet]]></description><link>https://claybrainer.com/kubernetes-fundamentals-high-level</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-fundamentals-high-level</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubernetes architecture]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 24 May 2026 17:13:50 GMT</pubDate><content:encoded><![CDATA[<p>Let’s understand some basic Kubernetes concepts. Trust me if the fundamentals are clear, becomes much easier to understand other concepts.</p>
<h1>Kubernetes Basics:</h1>
<p>So, what exactly is Kubernetes? — Kubernetes is an <strong>orchestration tool</strong>.</p>
<p>Now you might be wondering: <strong>“What does an orchestration tool actually mean?”</strong></p>
<p>In simple terms, an orchestration tool is something that manages and coordinates multiple components and ensures a <strong>desired outcome</strong> is achieved automatically. Think of it like a manager in an office. Instead of employees, Kubernetes manages containers and makes sure everything runs as expected.</p>
<p>In the Kubernetes world, it manages things like:</p>
<ul>
<li><p>Pods</p>
</li>
<li><p>Deployments</p>
</li>
<li><p>Scaling applications</p>
</li>
<li><p>Self-healing</p>
</li>
<li><p>Rolling updates</p>
</li>
</ul>
<p>And most importantly, it ensures your applications remain <strong>up and running</strong>.</p>
<p>For example, imagine your application pod crashes unexpectedly. Would you manually log in and restart it every time? Of course not.</p>
<p>This is where Kubernetes shines. Kubernetes detects the failure and automatically recreates the pod to bring your application back to the desired state. That is one of the core ideas behind Kubernetes — <strong>desired state management</strong>.</p>
<p>The Kubernetes have two major components <strong>Control Plane</strong> and <strong>the Worker Nodes.</strong> Let's understand this at high level</p>
<h2>Control Plane (Brain):</h2>
<p>Think of the <strong>Control Plane</strong> as the <strong>brain of Kubernetes</strong>. It decides <strong>what should happen, when it should happen, and where it should happen</strong>. The Control Plane is made up of the following components:</p>
<ul>
<li><p>API Server - Entry Point - You can think of it as the <strong>front door of Kubernetes</strong>.</p>
</li>
<li><p>Scheduler - Decides where the pod should run</p>
</li>
<li><p>Controller Manager - Ensures Desired state. The <strong>Controller Manager</strong> continuously checks whether the cluster is in the <strong>desired state</strong>.</p>
</li>
<li><p>ETCD - The Heart of Kubernetes. ETCD is a <strong>distributed key-value database</strong> where Kubernetes stores all information. <strong>Everything Kubernetes knows is stored in ETCD.</strong></p>
</li>
</ul>
<h2>Worker Node (The Muscle Power)</h2>
<p>As the name suggests, this is where the <strong>actual workload runs</strong>. Your applications, pods, and containers are executed inside Worker Nodes.</p>
<ul>
<li><p>Kubelet – The Communicator - The Kubelet continuously talks to the <strong>Control Plane</strong> and performs action on the node based on configuration. Think of Kubelet as the <strong>bridge between the Control Plane and Worker Node</strong>.</p>
</li>
<li><p>Container Runtime - Runs container (Docker/Containerd). The <strong>Container Runtime</strong> is responsible for actually running containers.</p>
</li>
<li><p>kube-proxy - The <strong>kube-proxy</strong> is responsible for <strong>network communication inside the cluster</strong>.</p>
</li>
</ul>
<h2>Core Building Blocks</h2>
<ul>
<li><p>pod - Smallest unit, Runs one more more container</p>
</li>
<li><p>Deployment- Manager pod, handles scaling, rolling updates and self healing</p>
</li>
<li><p>ReplicaSet - Ensures desired number of pods are always running. Deployment uses replica set internally</p>
</li>
<li><p>Labels and Selectors - An internal parameter which is used to define/refer and identify pod or other components</p>
</li>
<li><p>Namespace : Logical Isolation</p>
</li>
</ul>
<h2>How everything works together</h2>
<p>Consider that you have a Manifest file where you run the command <code>kubectl apply -f app.yaml</code></p>
<ul>
<li><p><strong>Control Plane</strong></p>
<ul>
<li><p>API Server receives request. The API Server validates the request and accepts the configuration.</p>
</li>
<li><p>Once validated, Kubernetes stores the desired configuration in <strong>ETCD</strong>.</p>
</li>
<li><p>Controller Manager - Notices the new entry in ETCD that a new deployment needs x replicas it updates the cluster state and creates the required pod definitions. (Note the pods are not created yet only it updates the configuration)</p>
</li>
<li><p>Scheduler - Identifies that X replicas of pods needs to be created and check the configuration and pick a suitable node and assign to the pods</p>
</li>
</ul>
</li>
<li><p><strong>Woker Node</strong></p>
<ul>
<li><p>Kublet which runs on each worker node keeps monitoring Control Plane, the moment it sees the pod scheduled to its node it starts the creation of pod.</p>
</li>
<li><p>Container Runtime : The <strong>Container Runtime</strong> pulls the required image and starts the containers.</p>
</li>
<li><p>kube-proxy : Once the pods are running the <strong>CNI plugin</strong> assigns IP addresses to pods, while <strong>kube-proxy</strong> manages routing and communication between services and pods.</p>
</li>
</ul>
</li>
</ul>
<p>This is the <strong>high-level end-to-end flow of Kubernetes</strong> and how all the components work together.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Networking Tools: A Layer-by-Layer Guide to What Fits Where]]></title><description><![CDATA[If you are in the DevOps world, especially the Kubernetes world chances are you have heard a lot of buzz around Kubernetes networking tools. And recently, with the announcement around NGINX Ingress de]]></description><link>https://claybrainer.com/kubernetes-networking-tools-a-layer-by-layer-guide-to-what-fits-where</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-networking-tools-a-layer-by-layer-guide-to-what-fits-where</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubernetes-services]]></category><category><![CDATA[Kubernetes-networking]]></category><category><![CDATA[#kubernetesNetworking]]></category><category><![CDATA[Kubernetes Networking Deep Dive]]></category><category><![CDATA[kubernetes-networking-tools]]></category><category><![CDATA[k8s]]></category><category><![CDATA[cilium]]></category><category><![CDATA[calico]]></category><category><![CDATA[eBPF]]></category><category><![CDATA[Devops]]></category><category><![CDATA[cloud native]]></category><category><![CDATA[cni]]></category><category><![CDATA[CNI-plugins]]></category><category><![CDATA[#ServiceMesh]]></category><category><![CDATA[service mesh]]></category><category><![CDATA[service mesh kubernetes]]></category><category><![CDATA[Gateway API]]></category><category><![CDATA[gatewayapi]]></category><category><![CDATA[#istio]]></category><category><![CDATA[istio service mesh]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 17 May 2026 09:42:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/93b0cd85-83df-49f2-bbef-89a054228d62.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are in the DevOps world, especially the Kubernetes world chances are you have heard a lot of buzz around <strong>Kubernetes networking tools</strong>. And recently, with the announcement around <strong>NGINX Ingress deprecation</strong>, you might be seeing even more discussions on social media about alternatives, new tools, and opinions flying all over the place.</p>
<p>But if you are someone like me, you might have paused for a second and wondered:</p>
<blockquote>
<p><em>“Wait… what exactly are all these tools?”</em><br /><em>“Where are they actually used?”</em><br /><em>“Why do we even need so many networking components in Kubernetes?”</em></p>
</blockquote>
<p>Trust me, I had the exact same questions.</p>
<p>At first, terms like <strong>Ingress</strong>, <strong>Gateway API</strong>, <strong>CNI</strong>, <strong>Service Mesh</strong>, <strong>Cilium</strong>, <strong>Istio</strong>, <strong>Envoy</strong>, <strong>MetalLB</strong>, and <strong>Load Balancer</strong> can feel like a giant networking soup 🍜.</p>
<p>But here’s the interesting part: Most of these tools are not competing with each other. Instead, <strong>they solve different networking problems at different layers of Kubernetes</strong>.</p>
<p>So in this blog, we are going to take a fun and practical deep dive into:</p>
<p>✅ Kubernetes networking basics<br />✅ What happens when traffic enters a Kubernetes cluster<br />✅ Which networking tools are used where<br />✅ When to use what (without getting confused!)</p>
<p>By the end of this blog, whenever someone mentions a Kubernetes networking tool, you should be able to say:</p>
<blockquote>
<p><em>“Ah okay, I know exactly where this fits in.”</em></p>
</blockquote>
<p>So grab your coffee ☕ (or tea 😄), and let’s get started.</p>
<h1>The Basics: How traffic flows in Kubernetes ?</h1>
<p>Before we start throwing around fancy names like <strong>Cilium</strong>, <strong>Istio</strong>, <strong>Gateway API</strong>, <strong>Envoy</strong>, <strong>MetalLB</strong>, or <strong>Service Mesh</strong>, let’s first understand something important:</p>
<p><strong>How does a normal request travel inside Kubernetes?</strong> Because once we understand the journey of a request, understanding networking tools becomes much easier.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/8e0a7525-039c-4373-a799-c61f917abf6e.png" alt="" style="display:block;margin:0 auto" />

<p>When a request comes from the internet to your application running inside Kubernetes, it goes through multiple layers before reaching the actual Pod.</p>
<p>A request flowing inside Kubernetes typically goes through the following stages:</p>
<blockquote>
<p><strong>Internet → Ingress / Gateway Layer → Service Layer → Finds the Correct Backend Pod → Pod Networking Layer → Delivers Traffic to the Pod → Application Pod → Serves the Request</strong></p>
</blockquote>
<p>The request originates from an external user or application. For example: <code>https://myapp.com</code></p>
<p>At this point, Kubernetes needs a way to expose your application to the outside world. So the traffic enters the cluster through the:</p>
<h3>Ingress / Gateway Layer</h3>
<ul>
<li><p><strong>Ingress / Gateway Layer → Controls External Traffic.</strong></p>
</li>
<li><p><strong>This layer acts as the entry point of the cluster.</strong></p>
</li>
</ul>
<p>Here Kubernetes decides:</p>
<blockquote>
<ul>
<li><p>Which application/service the request should go to URL/path-based routing (/payment, /user, /orders)</p>
</li>
<li><p>Authentication and authorization</p>
</li>
<li><p>TLS/HTTPS termination</p>
</li>
<li><p>Rate limiting and traffic filtering</p>
</li>
</ul>
</blockquote>
<p>Once the request rules are evaluated, the traffic is forwarded to the:</p>
<h3>Service Layer</h3>
<ul>
<li><p><strong>Service Layer → Finds the Correct Backend Pod</strong></p>
</li>
<li><p>Services act as a stable endpoint for Pods. Since Pods are temporary and their IPs can change, Services help Kubernetes reliably find the correct application Pods.</p>
</li>
</ul>
<p>Here Kubernetes:</p>
<blockquote>
<ul>
<li><p>Finds matching Pods using labels</p>
</li>
<li><p>Performs load balancing Routes traffic to healthy Pods</p>
</li>
<li><p>Enables service-to-service communication</p>
</li>
</ul>
</blockquote>
<p>Once the correct Pod endpoint is identified, traffic is forwarded through:</p>
<h2>Pod Networking Layer</h2>
<ul>
<li><p><strong>Pod Networking Layer → Delivers Traffic to the Pod</strong></p>
</li>
<li><p>This is where the actual packet routing happens. The networking layer ensures Pods can communicate: <em><strong>Within the same node, Across different nodes, Across namespaces, Across the cluster.</strong></em></p>
</li>
</ul>
<p>Here Kubernetes networking components:</p>
<blockquote>
<ul>
<li><p>Assign IP addresses to Pods,</p>
</li>
<li><p>Route traffic between Pods</p>
</li>
<li><p>Handle inter-node communication</p>
</li>
<li><p>Apply networking rules</p>
</li>
</ul>
</blockquote>
<p>Finally, the request reaches:</p>
<h3>Application Pod</h3>
<ul>
<li><p><strong>Application Pod → Serves the Request</strong></p>
</li>
<li><p>This is where your actual application runs. The Pod processes the request and sends the response back through the same networking path.</p>
</li>
</ul>
<h2>Security &amp; Observability → Monitors the Entire Journey</h2>
<p>While the request travels through Kubernetes, security and monitoring tools continuously observe the traffic.</p>
<p>This layer helps with:</p>
<blockquote>
<ul>
<li><p>Restricting communication between services</p>
</li>
<li><p>Monitoring traffic flow Tracking latency and failures</p>
</li>
<li><p>Applying network policies</p>
</li>
<li><p>Observability and troubleshooting</p>
</li>
</ul>
</blockquote>
<p>This happens across all layers, not just at one specific point.</p>
<p>Now that we understand <strong>how traffic flows inside Kubernetes</strong>, the next obvious question is:</p>
<blockquote>
<p><strong>What tools are used at each layer, and what problem do they solve?</strong></p>
</blockquote>
<h1>Deep Dive into Kubernetes Networking Tools</h1>
<p>As we saw earlier, a request travels through different networking layers before reaching the application Pod.</p>
<p>Each layer has its own responsibility, and naturally, different tools are built to solve problems at those specific layers. So let’s deep dive into the tools <strong>layer by layer</strong> and understand:</p>
<ul>
<li><p><strong>What does this layer handle?</strong></p>
</li>
<li><p><strong>Popular Tools Available</strong></p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/9889b970-4709-47bd-b41d-efbfa14e6ecb.png" alt="" style="display:block;margin:0 auto" />

<h2>1. Ingress/Gateway Layer:</h2>
<p>As discussed earlier, the <strong>Ingress/Gateway Layer</strong> acts as the <strong>entry point into the Kubernetes cluster</strong>. This layer is mainly responsible for handling <strong>incoming external traffic</strong> and deciding <strong>how requests should enter and move inside the cluster</strong>.</p>
<h3>What does this layer handle?</h3>
<p>This layer controls:</p>
<ul>
<li><p><strong>Which application/service the request should go to</strong></p>
</li>
<li><p><strong>URL / Path-based routing</strong></p>
</li>
<li><p><strong>Authentication &amp; Authorization</strong></p>
</li>
<li><p><strong>TLS / HTTPS Termination - Handling HTTPS Encryption</strong></p>
</li>
<li><p><strong>Rate Limiting &amp; Traffic Filtering</strong></p>
</li>
</ul>
<h3>Popular Tools Used in the Ingress / Gateway Layer</h3>
<ul>
<li><p><strong>Ingress Controllers:</strong> NGINX Ingress Controller, HAProxy Ingress, Traefik Ingress, AWS Load Balancer Controller, Azure Application Gateway Ingress Controller</p>
</li>
<li><p><strong>NGINX Gateway / Gateway API:</strong> Modern implementation for managing external traffic using the <strong>Kubernetes Gateway API</strong>.</p>
</li>
<li><p><strong>Traefik</strong>: A lightweight and cloud-native ingress/gateway solution.</p>
</li>
<li><p><strong>Cilium Gateway:</strong> Part of the <strong>Cilium ecosystem</strong>. Uses <strong>eBPF-powered networking</strong> and implements <strong>Gateway API</strong> capabilities.</p>
</li>
<li><p><strong>Istio Gateway:</strong> Part of the <strong>Istio Service Mesh</strong> ecosystem.</p>
</li>
</ul>
<h2>2. Service Layer Tools</h2>
<p>Once the traffic enters the cluster through the <strong>Ingress/Gateway Layer</strong>, it is forwarded to a <strong>Kubernetes Service</strong>.</p>
<p>But here’s the challenge: Pods in Kubernetes are <strong>temporary (ephemeral)</strong>. So how does Kubernetes make sure traffic always reaches the correct Pod? This is where the <strong>Service Layer</strong> comes into play.</p>
<p>The Service Layer acts as the <strong>traffic manager inside the cluster</strong> and ensures requests are routed reliably to healthy application Pods.</p>
<h3>What Does the Service Layer Handle?</h3>
<ul>
<li><p><strong>Service Discovery:</strong> Services help applications find each other without hardcoding Pod IPs.</p>
</li>
<li><p><strong>Load Balancing Across Pods:</strong> Traffic is distributed across Pods.</p>
</li>
<li><p><strong>Service-to-Service Communication:</strong> Inside Kubernetes, services constantly communicate with each other.</p>
</li>
<li><p><strong>Internal Traffic Encryption (mTLS):</strong> Normally, traffic inside the cluster is <code>Plain Text</code>. Because TLS is often terminated at the Ingress layer. But industries like Banking, Healthcare etc may require <strong>Encrypted communication between services inside the cluster as well.</strong> This is where <strong>mTLS (Mutual TLS)</strong> becomes important.</p>
</li>
</ul>
<h3>Popular Tools Used in the Service Layer</h3>
<ul>
<li><p><strong>kube-proxy:</strong> This is the <strong>default Kubernetes networking component</strong> responsible for service routing.</p>
</li>
<li><p><strong>Istio:</strong> One of the most popular <strong>Service Mesh</strong> tools. Istio adds advanced traffic management between services.</p>
</li>
<li><p><strong>Linkerd:</strong> A lightweight Service Mesh alternative.</p>
</li>
<li><p><strong>Consul Service Mesh:</strong> Another Service Mesh offering. It works beyond Kubernetes as well.</p>
</li>
<li><p><strong>Cilium Service Mesh:</strong> If you are already using <strong>Cilium</strong>, it can also provide <strong>Service Mesh capabilities</strong> without traditional sidecars.</p>
</li>
</ul>
<p><strong>Note:</strong> One important thing to understand is that <strong>not all tools provide the same capabilities</strong>.</p>
<p>For example, <strong>kube-proxy</strong> handles <strong>service routing and load balancing</strong>, but it does <strong>not provide features like mTLS (Mutual TLS), advanced traffic management, or observability</strong>.</p>
<p>If your requirement includes <strong>secure service-to-service communication (mTLS)</strong>, traffic control, retries, or advanced visibility, you would typically need a <strong>Service Mesh</strong> like <strong>Istio</strong>, <strong>Linkerd</strong>, <strong>Consul</strong>, or solutions like <strong>Cilium Service Mesh</strong>.</p>
<p>The tools listed above are some of the <strong>popular options</strong>, and the right choice depends on <strong>your use case, complexity, security requirements, and operational needs</strong>.</p>
<h2>3. Pod Networking Tools</h2>
<p>Now the request knows <strong>which Pod it should go to</strong>. But another important question comes up:</p>
<p>How does Kubernetes know:</p>
<ul>
<li><p>Where the Pod is?</p>
</li>
<li><p>What its IP address is?</p>
</li>
<li><p>How to route traffic across nodes?</p>
</li>
<li><p>How Pods communicate reliably?</p>
</li>
</ul>
<p>This is where <strong>Pod Networking</strong> comes into play. Honestly, this is one of the most important layers in Kubernetes networking because:</p>
<blockquote>
<p><strong>Without Pod Networking, Pods simply cannot talk to each other.</strong></p>
</blockquote>
<h3>What Does Pod Networking Handle?</h3>
<ul>
<li><p><strong>Assigning IP Addresses to Pods</strong>: Every Pod gets its own IP address</p>
</li>
<li><p><strong>Pod-to-Pod Communication:</strong> Within the same node, <strong>Across different nodes</strong>, Across namespaces <strong>without requiring NAT or complicated translations</strong>.</p>
</li>
<li><p><strong>Cross-Node Networking:</strong> A CNI((Container Network Interface)) plugin handles the actual networking implementation.</p>
</li>
<li><p><strong>Traffic Routing Between Nodes:</strong> This decide, how traffic moves, which node owns which pod IP range, how traffic gets routed</p>
</li>
<li><p><strong>Network Policies:</strong> This layer can also enforce rules. Just because Pods can communicate doesn’t mean they always should. Security restrictions matter.</p>
</li>
</ul>
<h3>Popular Tools Used in Pod Networking</h3>
<ul>
<li><p><strong>Flannel</strong>: One of the simplest Kubernetes networking tools. Flannel focuses mainly on <strong>Pod-to-Pod communication.</strong> This doesn't have all the above capabilities.</p>
</li>
<li><p><strong>Calico:</strong> One of the most widely used Kubernetes networking solutions. This has <strong>Strong Network Policy support.</strong> Preferred choice in Enterprise, Prod Cluster and Security-focused environments</p>
</li>
<li><p><strong>Cilium:</strong> Probably one of the hottest Kubernetes networking tools right now. It uses eBPF for traffic communication instead of traditional Linux Networking. <strong>Cilium is a complete ecosystem which span across all layers</strong></p>
</li>
<li><p><strong>AWS VPC CNI / Cloud Provider CNIs:</strong> Cloud providers also offer native networking plugins. AWS VPC CNI, Azure CNI, Google Kubernetes Engine CNI</p>
</li>
</ul>
<blockquote>
<p><strong>Note:</strong> In real world Kubernetes environments, it is common to use multiple networking tools together.</p>
<p>For example, in cloud-managed Kubernetes services like EKS, companies often use the cloud-native CNI plugin (such as AWS VPC CNI) for Pod communication and add tools like Calico or Cilium on top for additional features such as Network Policies, security, and observability.</p>
</blockquote>
<h2>4. Security &amp; Observability Tools</h2>
<p>At this point, traffic is successfully flowing inside the cluster. Requests are reaching the correct Pods. But here comes an important question:</p>
<blockquote>
<p><strong>How do we control who can talk to whom?</strong><br /><strong>How do we monitor what is happening inside the cluster?</strong></p>
</blockquote>
<p>This is where <strong>Security &amp; Observability tools</strong> come into the picture.</p>
<p>This layer helps us:</p>
<ul>
<li><p>Restrict communication between Pods and services</p>
</li>
<li><p>Monitor traffic flow</p>
</li>
<li><p>Debug networking problems</p>
</li>
<li><p>Understand service dependencies</p>
</li>
<li><p>Improve visibility into cluster communication</p>
</li>
</ul>
<h3>What Does This Layer Handle?</h3>
<ul>
<li><p><strong>Network Policies:</strong> Who can communicate with whom</p>
</li>
<li><p><strong>Traffic Observability:</strong> Adding detailed visibility to the network flows</p>
</li>
<li><p><strong>Security Monitoring:</strong> Identifying unusual traffic, Policy violations etc</p>
</li>
</ul>
<h3>Popular Tools Used in This Layer</h3>
<ul>
<li><p><strong>Kubernetes Network Policies:</strong> This is the <strong>native Kubernetes way</strong> to restrict Pod communication. <strong>Kubernetes Network Policies alone do not enforce anything you need compatible CNI like cilium or calico</strong></p>
</li>
<li><p><strong>Calico:</strong> Apart from Pod networking, Calico provides strong Network Policy enforcement, Traffic Filtering, and Security Isolation</p>
</li>
<li><p><strong>Cilium + Hubble:</strong> Cilium takes <strong>security and observability to another level</strong>, along with many of the features offered by Calico. It provides advanced networking, security, and traffic visibility capabilities. <strong>Hubble</strong> is Cilium’s visualization tool</p>
</li>
<li><p><strong>Istio Observability:</strong> Similar to Cilium, <strong>Istio</strong> also provides <strong>security and observability features</strong>. If you are already using Istio as your <strong>Service Mesh</strong>, you can make use of its built-in capabilities.</p>
</li>
</ul>
<h1>Conclusion</h1>
<p>By now, you should have a better understanding of the <strong>different Kubernetes networking tools</strong>, <strong>where they fit in the networking flow</strong>, and <strong>what problem each of them is trying to solve</strong>.</p>
<p>Some tools help with:</p>
<ul>
<li><p>Exposing applications to the outside world (<strong>Ingress / Gateway</strong>)</p>
</li>
<li><p>Service-to-service communication (<strong>Service Layer / Service Mesh</strong>)</p>
</li>
<li><p>Pod communication across nodes (<strong>CNI / Pod Networking</strong>)</p>
</li>
<li><p>Security, visibility, and traffic monitoring (<strong>Observability &amp; Network Policies</strong>)</p>
</li>
</ul>
<p>So the next time you hear terms like <strong>Cilium</strong>, <strong>Calico</strong>, <strong>Istio</strong>, <strong>NGINX Gateway</strong>, or <strong>Traefik</strong>, hopefully you will have a clearer idea of:</p>
<blockquote>
<p><strong>Where they fit and what their purpose is.</strong></p>
</blockquote>
<p>For a quick recap and better understanding, feel free to refer to the <a href="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/9889b970-4709-47bd-b41d-efbfa14e6ecb.png">Flow Diagram</a>.</p>
<p>I hope this blog helped simplify Kubernetes networking a little 😄</p>
<p><strong>Happy Learning! 🚀</strong><br />See you in the next one.</p>
]]></content:encoded></item><item><title><![CDATA[Terraform Insights - Part 1 : How to Structure Terraform for Large-Scale Projects]]></title><description><![CDATA[When working with Terraform in real-world environments, it’s not just about writing .tf files, it’s about understanding the small details that make a big difference in reliability, scalability, and ma]]></description><link>https://claybrainer.com/terraform-insights-how-to-structure-terraform-for-large-scale-projects</link><guid isPermaLink="true">https://claybrainer.com/terraform-insights-how-to-structure-terraform-for-large-scale-projects</guid><category><![CDATA[Terraform]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[IaC (Infrastructure as Code)]]></category><category><![CDATA[Terraform workspace]]></category><category><![CDATA[terraform-module]]></category><category><![CDATA[terraform-modular-approach]]></category><category><![CDATA[terraform-large-projects]]></category><category><![CDATA[Devops]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Platform Engineering ]]></category><category><![CDATA[SRE]]></category><category><![CDATA[terraform-cloud]]></category><category><![CDATA[terraform-state]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Mon, 11 May 2026 03:49:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/bfe2f4e5-f5fe-4c36-b837-21bec6d0f6ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working with Terraform in real-world environments, it’s not just about writing <code>.tf</code> files, it’s about understanding the small details that make a big difference in reliability, scalability, and maintainability.</p>
<p>In this blog, we’ll explore some important Terraform insights and nitty-gritties that are extremely useful for day to day work. These are the kind of concepts that not only make you better at Terraform but also give you an edge in technical discussions and interviews.</p>
<p>The blog is structured in a simple and practical way. Each topic will cover:</p>
<ul>
<li><p><strong>What it is</strong> – A clear explanation of the concept</p>
</li>
<li><p><strong>Why and Where it is used</strong> – Real-world scenarios where it applies</p>
</li>
<li><p><strong>How to use it</strong> – Practical usage with Terraform</p>
</li>
<li><p><strong>Example</strong> – So you can relate and apply it immediately</p>
</li>
</ul>
<p>This approach ensures that you don’t just understand the concept, you know exactly how to use it.</p>
<p>Let’s get started 🚀</p>
<h1>How do we structure the terraform code for large project</h1>
<p>One of the most common questions we encounter is: <strong>“How do we structure Terraform code for a large project?”</strong></p>
<p>This becomes even more critical when working on a <strong>greenfield setup</strong>, where everything is being built from scratch. The decisions you make at this stage will directly impact how well your infrastructure scales, how easy it is to maintain, and how effectively teams can collaborate.</p>
<p>So, how do we design a Terraform project that not only works today but also scales seamlessly in the future? Let’s dive deeper into this.</p>
<h1>What it is ?</h1>
<p>Before diving into <em>how</em> to structure a Terraform project, let’s first get the basics clear: <strong>What exactly are we structuring? What are we really talking about here?</strong></p>
<p>Every Terraform project is made up of a set of core files and components. The real challenge is not creating these files, it’s about <strong>organizing them in a way that scales with your infrastructure</strong>.</p>
<p><strong>Core Components in a Terraform Project</strong></p>
<ul>
<li><p><strong>Modules</strong>: Modules are the <strong>building blocks</strong> of any Terraform project. It helps you to <strong>Reuse Code, Maintain Consistency and Avoid Duplication.</strong> A typical module consists of:</p>
<ul>
<li><p><a href="http://main.tf"><code>main.tf</code></a> → Defines the resources</p>
</li>
<li><p><a href="http://variables.tf"><code>variables.tf</code></a> → Input variables.</p>
</li>
<li><p><a href="http://outputs.tf"><code>outputs.tf</code></a> → Exposes values</p>
</li>
<li><p><a href="http://provider.tf"><code>provider.tf</code></a> → Provider configuration</p>
</li>
<li><p><a href="http://versions.tf"><code>versions.tf</code></a> → Terraform &amp; provider version constraints</p>
</li>
<li><p><a href="http://data.tf"><code>data.tf</code></a> → Data sources</p>
</li>
<li><p><a href="http://backend.tf"><code>backend.tf</code></a> → Remote state configuration (sometimes kept separate mostly added with versions.tf)</p>
</li>
</ul>
</li>
<li><p><strong>State File Management</strong>: Terraform maintains a <strong>state file</strong> to track the current state of your infrastructure. Proper state management is critical for <strong>Preventing Conflicts, Ensuring Consistency and Enabling team collaboration.</strong></p>
<ul>
<li><p>Key considerations:</p>
<ul>
<li><p>Where is the state stored? (local vs remote),</p>
</li>
<li><p>How is state locking handled?</p>
</li>
<li><p>How do multiple users safely interact with it?</p>
</li>
<li><p><strong>⚠️ Pain Point: Large State File Slowing Down Terraform:</strong> When all resources are managed in a single Terraform state file, it grows significantly in size and slows down deployments because Terraform must evaluate the entire state during every run; to avoid this, it’s a best practice to split state files logically—either by resource type or architectural design to improve performance, scalability, and maintainability.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Multi-Environment &amp; Multi-Region Deployment:</strong> Real-world infrastructure is rarely single-environment. This involves <strong>Structuring Folders properly, Passing Environment specific variables, Managing isolated State files</strong></p>
<ul>
<li>You need to manage deployments across Environments (dev, stag and prod etc..) and across region (us-east-1, eu-west-1 etc..)</li>
</ul>
</li>
<li><p><strong>Input Variables &amp; Defaults Management:</strong> Variables make your Terraform code <strong>dynamic and reusable</strong>. Doing a right setup here will allow our code base to work across different region and enviornment</p>
<ul>
<li><p>Things to Consider</p>
<ul>
<li><p>Defining clear input variables</p>
</li>
<li><p>Setting sensible defaults</p>
</li>
<li><p>Overriding values per environment</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>So, when we talk about structuring Terraform, we are essentially talking about <strong>how we organize these building blocks effectively</strong>.</p>
<h3><strong>In Simple Terms</strong></h3>
<p>Structuring Terraform is about:</p>
<blockquote>
<p><strong>Organizing modules, state, variables, and environments in a way that your code scales along with your infrastructure without breaking performance, security, or maintainability.</strong></p>
</blockquote>
<p>Now we understand the what part let's discuss the why part.</p>
<h1>Why?</h1>
<p>Let's talk about why this is needed, As your infrastructure grows, your Terraform code should be able to:</p>
<ul>
<li><p><strong>Handle complexity without becoming messy:</strong> As more components (infra resource) are added, the codebase should remain clean, well organized, and easy to debug or navigate.</p>
</li>
<li><p><strong>Support multiple environments and regions:</strong> The same codebase should support deployments across different environments (dev, staging, prod) and regions with minimal or no changes to the core logic.</p>
</li>
<li><p><strong>Maintain performance and security:</strong> Execution performance and security standards should remain consistent or improve as the infrastructure scales, without degradation.</p>
</li>
<li><p><strong>Enable easy collaboration across teams:</strong> As infrastructure grows, more team members will contribute. The code should be structured in a way that enables seamless collaboration and reduces conflicts.</p>
</li>
</ul>
<p>By arranging them in this way helps you to grow your IaC seamlessly as your infrastructure grows.</p>
<h1><strong>How ?</strong></h1>
<p>When starting a greenfield Infrastructure as Code (IaC) project, there are multiple ways to structure and manage your infrastructure. Choosing the right approach early helps improve scalability, maintainability, collaboration, and operational efficiency as the infrastructure grows.</p>
<p>In this section, we will discuss two commonly used approaches for organizing Terraform based IaC projects:</p>
<ol>
<li><p><strong>Terraform Workspace Approach</strong></p>
</li>
<li><p><strong>Modular Folder-Based Approach</strong></p>
</li>
</ol>
<h2>Terraform Workspace Approach</h2>
<h3>What is Terraform Workspace?</h3>
<p>Terraform Workspaces allow you to manage multiple instances of the same infrastructure configuration using a single Terraform codebase.</p>
<p>Each workspace maintains its own separate state file, enabling you to deploy the same infrastructure into different environments such as:</p>
<ul>
<li>Development (<code>dev</code>), Testing (<code>test</code>), Staging (<code>stage</code>), Production (<code>prod</code>)</li>
</ul>
<p>Instead of maintaining separate folders or separate state files manually, Terraform automatically isolates the infrastructure state per workspace.</p>
<h3>Why Use Terraform Workspaces?</h3>
<p>As infrastructure grows, managing multiple environments becomes challenging. Teams often need identical infrastructure across environments with only minor differences such as:</p>
<ul>
<li>Instance size, Number of replicas, Database sizing</li>
</ul>
<p>Terraform Workspaces help reduce duplication by allowing the same code to be reused across environments while keeping the infrastructure states isolated.</p>
<p>This approach improves:</p>
<ul>
<li><p>Code reusability</p>
</li>
<li><p>Consistency across environments</p>
</li>
<li><p>Simpler maintenance</p>
</li>
<li><p>Faster onboarding</p>
</li>
</ul>
<h3>Where Can Terraform Workspaces Be Used?</h3>
<ol>
<li><p><strong>Multiple Similar Environments Exist</strong> - Dev, Qa, Prod where you will use same infra setup while only minor changes are required</p>
</li>
<li><p><strong>Platform or Shared Services Teams:</strong> Teams managing reusable infrastructure templates can deploy the same stack for different customers, regions, or business units.</p>
</li>
<li><p><strong>Temporary or Feature Environments:</strong> Useful fof Feature branch testing, Sandbox testing</p>
</li>
</ol>
<h3>How Terraform Workspace Works</h3>
<p>Consider a hotel where:</p>
<ul>
<li><p>The <strong>kitchen</strong> prepares a common base meal for all guests.</p>
</li>
<li><p>Every guest stays in a separate <strong>room</strong>.</p>
</li>
<li><p>Guests can customize their food in their own room by adding available ingredients such as:</p>
<ul>
<li><p>Pepper</p>
</li>
<li><p>Salt</p>
</li>
<li><p>Sauces</p>
</li>
<li><p>Spices</p>
</li>
</ul>
</li>
</ul>
<p>The chef does not cook completely different meals for every room. Instead, one common meal is prepared and each room customizes it based on individual preferences.</p>
<p><strong>Relating This to Terraform Workspaces</strong></p>
<p>In Terraform:</p>
<ul>
<li><p>The <strong>main Terraform code</strong> acts like the <strong>hotel kitchen</strong>.</p>
</li>
<li><p>Each <strong>workspace</strong> acts like a separate <strong>room/environment</strong>.</p>
</li>
<li><p>The common infrastructure code remains the same for all environments.</p>
</li>
<li><p>Each workspace can customize values such as:</p>
<ul>
<li><p>Instance size</p>
</li>
<li><p>Resource count</p>
</li>
<li><p>Naming conventions</p>
</li>
<li><p>Scaling configuration</p>
</li>
<li><p>Environment variables</p>
</li>
</ul>
</li>
</ul>
<p>without changing the core infrastructure code.</p>
<h3><strong>How to create workspace and work with that?</strong></h3>
<ul>
<li><p>To list the available workspace you use: <code>terraform workspace list</code></p>
</li>
<li><p>To Create new workspace : <code>terraform workspace new &lt;workspace name&gt;</code></p>
<ul>
<li><p><code>terraform workspace new dev</code></p>
</li>
<li><p><code>terraform workspace new prod</code></p>
</li>
</ul>
</li>
<li><p>Switch between workspace: <code>terraform workspace select dev</code></p>
</li>
</ul>
<p>In a real-world setup, Terraform Workspaces are commonly used by creating a separate workspace for each environment such as <code>dev</code>, <code>test</code>, and <code>prod</code>. The typical workflow looks like this:</p>
<ul>
<li><p>Create and maintain separate workspaces for each environment</p>
</li>
<li><p>Select the required workspace before performing any operation <code>terraform workspace select dev</code></p>
</li>
<li><p>Verify that you are in the correct workspace <code>terraform workspace show</code></p>
</li>
<li><p>Run Terraform commands, which will apply the infrastructure using the variables and state associated with that workspace <code>terraform apply</code></p>
</li>
</ul>
<h3>Pros:</h3>
<ul>
<li><p>Reduced Code Duplication</p>
</li>
<li><p>Easier Maintenance</p>
</li>
<li><p>Faster Environment Creation</p>
</li>
<li><p>Consistency Across Environments</p>
</li>
<li><p>Simplified State Isolation</p>
</li>
</ul>
<h3>Cons:</h3>
<ul>
<li><p><strong>Shared Backend Complexity:</strong> All workspaces typically share the same backend configuration. So all the backed files are in same location so you need to create a complex rules to restrict user to access the backend statefiles.</p>
</li>
<li><p><strong>Risk of Human Error:</strong> Accidentally applying changes in the wrong workspace can impact production.</p>
</li>
<li><p><strong>Difficult CI/CD Integration at Scale:</strong> As the number of environment increases Pipeline management becomes harder, Environment-specific approvals become complex, State access management becomes difficult</p>
</li>
<li><p><strong>Limited Environment Customization:</strong> Workspaces work best when environments are nearly identical.</p>
</li>
</ul>
<h3>Use Workspaces When</h3>
<p>✅ Infrastructure is mostly identical<br />✅ Environment differences are minimal<br />✅ Small-to-medium scale projects<br />✅ Rapid environment provisioning is needed</p>
<p>If that is the case how the other modular folder based approach solve the problem let's discuss that.</p>
<h2>Modular Folder-Based Approach</h2>
<p>The Modular Folder-Based Approach is a Terraform project structure where infrastructure is organized into:</p>
<ul>
<li><p><strong>Reusable Modules</strong></p>
</li>
<li><p><strong>Environment-specific folders</strong></p>
</li>
</ul>
<p>Instead of using a single Terraform configuration for all environments, each environment has its own dedicated folder and configuration while sharing reusable infrastructure modules. This approach separates:</p>
<ul>
<li><p>Reusable infrastructure logic</p>
</li>
<li><p>Environment-specific configurations</p>
</li>
</ul>
<p>making the infrastructure easier to scale, maintain, and manage. <em><strong>This is the industry wide popular approach used to manage large projects</strong></em></p>
<h3>Why Use the Modular Folder-Based Approach?</h3>
<p>As infrastructure grows, environments usually become different from each other. For example:</p>
<ul>
<li><p>Production may require: Multi-region deployment, High Availability, Autoscaling etc which is not needed for Dev</p>
</li>
<li><p>For Dev we can use low cost instances and minimum setup to test the code.</p>
</li>
</ul>
<p>Managing these differences using only Terraform Workspaces can become complicated. The Modular Folder-Based Approach solves this by:</p>
<ul>
<li><p>Separating environments completely</p>
</li>
<li><p>Reusing infrastructure components through modules</p>
</li>
<li><p>Allowing each environment to evolve independently</p>
</li>
</ul>
<h3>Where Can This Approach Be Used?</h3>
<p>As mentioned this is the industry wide popular approach,</p>
<ul>
<li><p>Enterprise Infrastructure</p>
</li>
<li><p>Production-Grade Platforms</p>
</li>
<li><p>Multi-Region Deployments</p>
</li>
<li><p>Microservices Platforms</p>
</li>
</ul>
<h3>How we Design this approach ?</h3>
<p>We will create a folder structure as mentioned below :</p>
<pre><code class="language-markdown">terraform/
├── modules/
│   ├── vpc/
│   ├── ec2/
│   ├── rds/
│   └── eks/
│
└── environments/
    ├── dev/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── terraform.tfvars
    │
    ├── test/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── terraform.tfvars
    │
    └── prod/
        ├── main.tf
        ├── variables.tf
        └── terraform.tfvars
</code></pre>
<p><strong>How It Works</strong></p>
<ul>
<li><p><strong>Modules Directory:</strong> Contains reusable infrastructure components. Each module is written once and reused across environments.</p>
</li>
<li><p><strong>Environment Directory:</strong> Each environment</p>
<ul>
<li><p>Maintains its own Terraform state</p>
</li>
<li><p>Has its own backend configuration</p>
</li>
<li><p>Uses its own variable values</p>
</li>
<li><p>Can independently deploy infrastructure</p>
</li>
</ul>
</li>
</ul>
<h3>Pros</h3>
<ul>
<li><p>One of the major advantages of the Modular Folder-Based Approach is that it helps reduce human error compared to Terraform Workspaces. In the workspace approach, users must manually switch between environments, and in real-world scenarios engineers may accidentally apply changes to the wrong workspace assuming they are already in the correct one. With the folder-based approach, each environment has its own dedicated directory such as <code>dev</code>, <code>test</code>, and <code>prod</code>, and users must explicitly navigate to the corresponding folder before running Terraform commands. This clear separation between environments greatly reduces the chances of accidental deployments and improves operational safety, especially for production infrastructure.</p>
</li>
<li><p><strong>Better Scalability:</strong> Works well with Large infrastructure, Multi-team organizations</p>
</li>
<li><p><strong>Reusable Infrastructure Components:</strong> Modules reduce duplication while keeping flexibility.</p>
</li>
<li><p><strong>Easier Customization:</strong> Each environment can use different modules, different architecture and can scale as per the need</p>
</li>
<li><p><strong>Better Security and Access Control</strong></p>
</li>
<li><p><strong>Easier CI/CD Integration</strong></p>
</li>
</ul>
<h3>Cons:</h3>
<ul>
<li><p>More Folder Management</p>
</li>
<li><p>Slightly More Initial Setup</p>
</li>
<li><p>Risk of Module Over-Engineering</p>
</li>
<li><p>Version Management Complexity</p>
</li>
</ul>
<h3>Best Practices</h3>
<ul>
<li><p>Keep Modules Small and Focused</p>
</li>
<li><p>Maintain Independent State Per Environment</p>
</li>
<li><p>Use Versioned Modules</p>
</li>
</ul>
<pre><code class="language-plaintext">source = "git::https://github.com/org/vpc-module.git?ref=v1.0.0"
</code></pre>
<h3>When to Use This Approach</h3>
<p>Use the Modular Folder-Based Approach when:</p>
<p>✅ Infrastructure differs across environments<br />✅ Large-scale systems are involved<br />✅ Strong isolation is required<br />✅ Multiple teams manage infrastructure<br />✅ Enterprise-grade CI/CD pipelines exist<br />✅ Security boundaries are important</p>
<h1>Conclusion</h1>
<p>The Modular Folder-Based Approach is one of the most scalable and production-friendly ways to organize Terraform infrastructure.</p>
<p>It provides:</p>
<ul>
<li><p>Reusable infrastructure through modules</p>
</li>
<li><p>Strong environment isolation</p>
</li>
<li><p>Better scalability</p>
</li>
<li><p>Easier customization</p>
</li>
<li><p>Safer production deployments</p>
</li>
</ul>
<p>While it introduces slightly more structure and setup effort initially, it becomes significantly easier to manage as infrastructure and teams grow.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Overpaying AWS: How to Save on Your Bill with Savings Plans]]></title><description><![CDATA[Few weeks back, I started exploring one simple question: 👉 “What options does AWS actually give me to reduce my bill?”
At first, I was just looking for discount Like, is there some hidden setting or ]]></description><link>https://claybrainer.com/stop-overpaying-aws-how-to-save-on-your-bill-with-savings-plans</link><guid isPermaLink="true">https://claybrainer.com/stop-overpaying-aws-how-to-save-on-your-bill-with-savings-plans</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-savings]]></category><category><![CDATA[aws savings plans]]></category><category><![CDATA[finops]]></category><category><![CDATA[AWS FinOps]]></category><category><![CDATA[cloud cost optimization  ]]></category><category><![CDATA[#CostOptimization ]]></category><category><![CDATA[Cost Optimization]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 26 Apr 2026 17:02:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/fa8653ef-49e1-4f5a-a75e-9b20ca7c7ee0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Few weeks back, I started exploring one simple question: 👉 <em>“What options does AWS actually give me to reduce my bill?”</em></p>
<p>At first, I was just looking for discount Like, is there some hidden setting or plan AWS offers that I’m missing?. But as I dug deeper, I got introduced to a whole new world <strong>FinOps</strong>. And honestly, this is something many of us (especially in small companies) either:</p>
<ul>
<li><p>don’t know about, or</p>
</li>
<li><p>know, but haven’t really implemented properly.</p>
</li>
</ul>
<p>Here’s the surprising part, when I checked many organisation are still not aware of this. When I started looking into it seriously, I realized, We could potentially save <strong>up to ~15% of our AWS bill</strong> just by enabling few things. That’s not small.</p>
<p>So in this blog, I’ll walk through:</p>
<ul>
<li><p>what options AWS gives us to reduce cost</p>
</li>
<li><p>where most people miss out</p>
</li>
<li><p>and some practical ways you can actually start saving</p>
</li>
</ul>
<hr />
<p>Let’s get started 👇</p>
<p>Even though there are many ways to optimize AWS costs, in this blog I’m going to focus on one of those: <strong>The Savings Plan</strong></p>
<h1>💡 Savings Plans</h1>
<p>AWS <em>really</em> wants you to use <strong>Savings Plans</strong>. At a high level, Savings Plans are AWS’s way of saying:</p>
<blockquote>
<p>👉 <em>“Commit to using a certain amount of compute, and I’ll give you a discount.”</em></p>
</blockquote>
<p>Simple idea. But where most people get confused is <em>what exactly are we committing to?</em></p>
<p>🧠 What are you actually committing? You’re not committing to:</p>
<ul>
<li><p>a specific Instance</p>
</li>
<li><p>a specific region</p>
</li>
<li><p>or even a specific service</p>
</li>
</ul>
<p>Instead you are committing to:</p>
<blockquote>
<p>👉 <strong>A fixed hourly spend (in $/hour) for 1 or 3 years.</strong></p>
</blockquote>
<p>Think of it like telling AWS: “<em>Hey, I’m committing to spend around $10/hour for the next year what kind of discount can you give me?”</em>. AWS will offer you discount and as long as your usage matches that commitment you save money.</p>
<h2>🔍 Types of Savings Plans</h2>
<p>At the time of writing this blog, AWS offers four types of Savings Plans. But honestly, you only need to care about two of them right now because that’s where you’ll see the biggest savings (highlighted below):</p>
<ul>
<li><p><strong>Compute Savings</strong> ✅</p>
</li>
<li><p><strong>EC2 Instance Savings Plan</strong> ✅</p>
</li>
<li><p>Database Savings plan</p>
</li>
<li><p>SageMaker Savings plan</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/0a06f2b0-44cc-4225-b382-0c83776bb9a8.png" alt="claybrainers-aws-savingsplan-types" style="display:block;margin:0 auto" />

<h3><strong>Compute Savings</strong></h3>
<p>This is the most flexible option you have and honestly, this is where most teams start. Why? Because there are very few restrictions. The trade off is simple: you get slightly lower discounts compared to other plans, but a lot more freedom.</p>
<p>You don’t need to worry about</p>
<ul>
<li><p>instance family,</p>
</li>
<li><p>instance type,</p>
</li>
<li><p>or even region.</p>
</li>
</ul>
<p>Just commit to a certain hourly spend, and AWS automatically applies the discount across your usage.</p>
<p>Another big advantage is coverage. It applies to a wide range of services like <strong>EC2, Fargate, Lambda</strong>, and more so you’re not locked into a single service. This makes it a great fit when your workload is unpredictable or when you're using a mix of different services across your architecture. In this options the discounts will be in the range of around 10% to 15% approx at the time of writing this blog.</p>
<h3>Example:</h3>
<p>Let’s say:</p>
<ul>
<li><p>Your average usage = <strong>$10/hour --&gt;</strong> This is what you commit to AWS</p>
</li>
<li><p>AWS Says I will give you <strong>15% discount</strong></p>
</li>
</ul>
<p>So instead of paying \(10/hour, you effectively pay: 👉 <strong>\)8.5/hour</strong></p>
<p>Over a month (~730 hours):</p>
<ul>
<li><p>Without Savings Plan → \(10 × 730 = <strong>\)7,300</strong></p>
</li>
<li><p>With Compute Plan → \(8.5 × 730 = <strong>\)6,205</strong></p>
</li>
</ul>
<p>💡 <strong>You save ~$1,095/month</strong></p>
<p>And the best part? This applies across services like EC2, Fargate, Lambda—no restrictions.</p>
<h3>EC2 Instance Savings Plans</h3>
<p>As the name suggests, this plan is specifically for <strong>EC2 instances</strong>. It comes with a few more restrictions compared to Compute Savings Plans but in return, you get <strong>better discounts</strong>.</p>
<p>When I say “restrictions,” here’s what I mean:</p>
<ul>
<li><p>You’re tied to a <strong>specific instance family</strong> (e.g., t3, m5)</p>
</li>
<li><p>You’re tied to a <strong>specific region</strong></p>
</li>
</ul>
<p>That said, AWS still gives you some flexibility where it matters. You can freely change:</p>
<ul>
<li><p>Instance size (e.g., t3.small → t3.large)</p>
</li>
<li><p>Operating system (Linux, Windows, etc.)</p>
</li>
<li><p>Tenancy (shared, dedicated)</p>
</li>
</ul>
<p>So you’re not completely locked in just scoped within a boundary. In simple terms, you’re telling AWS:</p>
<blockquote>
<p>“Hey, I commit to using this EC2 instance family in this region for the next year (or three) for X amount of usage—what discount can you give me?”</p>
</blockquote>
<p>Because of this tighter commitment, the <strong>discounts here are higher</strong> compared to Compute Savings Plans.</p>
<h3>Example:</h3>
<p>Now let’s take the same scenario:</p>
<ul>
<li><p>You commit to <strong>$10/hour</strong></p>
</li>
<li><p>But this time for a <strong>specific EC2 family + region</strong></p>
</li>
<li><p>AWS gives you <strong>40% discount</strong> (<em>Discount rate is an example may vary in real time</em>)</p>
</li>
</ul>
<p>So you effectively pay: 👉 <strong>$6/hour</strong></p>
<p>Over a month:</p>
<ul>
<li><p>Without Savings Plan → $7,300</p>
</li>
<li><p>With EC2 Plan → \(6 × 730 = <strong>\)4,380</strong></p>
</li>
</ul>
<p>💡 <strong>You save ~$2,920/month</strong></p>
<h3>What happens if your usage goes above $10/hour?</h3>
<p>So what happens when you use beyond your commitment? AWS keeps track of your <strong>usage every single hour</strong>. As long as your total eligible usage in that hour is <strong>within $10/hour</strong>, you’ll get the applicable discount.</p>
<p>But the moment you go beyond that: The <strong>extra usage (above $10/hour)</strong> is charged at <strong>On-Demand pricing(no discount)</strong>.</p>
<p>👉 Example:</p>
<ul>
<li><p>You committed → $10/hour</p>
</li>
<li><p>Actual usage → $12/hour</p>
</li>
</ul>
<p>Then:</p>
<ul>
<li><p>First $10/hour → discounted (15%)</p>
</li>
<li><p>Remaining $2/hour → normal On-Demand pricing</p>
</li>
</ul>
<p>But AWS does give you flexibility in one direction: You can <strong>increase your total $/hour commitment anytime</strong> by purchasing additional Savings Plans even in the middle of your current plan. You <strong>cannot decrease or edit</strong> your existing commitment. <strong>This is applicable for both the savings plans.</strong> This gives us flexiblity to increase our commitment cost if we find out that our average usage is more than what we commit.</p>
<p>Now that we understand what Savings Plans are and the value they bring, the next obvious question is:</p>
<h3>👉 <strong>How much should I commit?</strong></h3>
<p>Committing the right number gives you the maximum advantage.</p>
<ul>
<li><p>If you <strong>overcommit</strong> → you’ll end up paying for unused capacity</p>
</li>
<li><p>If you <strong>undercommit</strong> → you won’t get the full discount benefit ❌</p>
</li>
</ul>
<p>So getting this number right is key.</p>
<h3>How to decide the right number?</h3>
<p>AWS already gives you a built in helper for this.</p>
<p>👉 It’s called <strong>“Recommendations”</strong> under Savings Plans.</p>
<p>This tool analyzes your historical usage and suggests an <strong>optimal $/hour commitment</strong> based on your actual workload.</p>
<h3>How to use it</h3>
<ul>
<li><p>Login to your AWS account &gt;&gt; Go to "<strong>Billing and Cost Management</strong>" &gt;&gt; From Left Side panel Expand <strong>Saving Plan &gt;&gt;</strong> Select <strong>Recommendations</strong></p>
</li>
<li><p>To Run recommendations</p>
<ul>
<li><p>Choose <strong>Compute Savings Plan</strong> (recommended for most cases) or Or <strong>EC2 Instance Savings Plan</strong></p>
</li>
<li><p>Select your preferences:</p>
<ul>
<li><p>Term → 1 year or 3 years</p>
</li>
<li><p>Payment option → No upfront / Partial / All upfront (Upfront will give you more discount but you can also opt for No upfront which falls on your normal billing cycle)</p>
</li>
<li><p>Lookback period → 7, 30, or 60 days (This is the sample peroid)</p>
</li>
</ul>
</li>
<li><p>AWS will automatically generate a recommendation for you. And it looks something like below.</p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>Note: The menu may vary when you check them this is taken at the time of writing the blog</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/6d6f96d6-4252-47f2-a1fa-4307525bdc80.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>👉 Start with <strong>30 days lookback + 1-year term + No upfront.</strong> This tells us that if we commit US\(0.750/hour for a 1-year term you could save an average of US\)0.15/hour that is 19% savings.</li>
</ul>
<p>Now once you conclude with the plan you can click on <strong>Add Savings plan to cart</strong> and purchase it. It will get reflected on your next billing cycle</p>
<p>You can also use purchase analyzer to see what is the right amount for your current environment and many other things.</p>
<h3>Purchase Analyzer:</h3>
<p>The <strong>Purchase Analyzer</strong> gives you a deeper view before you actually buy a Savings Plan. It helps you understand things like:</p>
<ul>
<li><p><strong>Cost comparison</strong> → How your current spend compares with the Savings Plan</p>
</li>
<li><p><strong>Coverage %</strong> → How much of your current usage will be covered by the plan</p>
</li>
<li><p><strong>Projected savings</strong> → What you actually save with this commitment</p>
</li>
</ul>
<p>👉 It helps you validate your decision before committing. To use this tool</p>
<ul>
<li><p>Login to your AWS account &gt;&gt; Go to "<strong>Billing and Cost Management</strong>" &gt;&gt; From Left Side panel Expand <strong>Saving Plan &gt;&gt;</strong> Select <strong>Purchase Analyzer</strong></p>
</li>
<li><p>Choose <strong>Compute Savings Plan</strong> (recommended for most cases) or Or <strong>EC2 Instance Savings Plan</strong></p>
</li>
<li><p>Select your preferences:</p>
<ul>
<li><p>Term → 1 year or 3 years</p>
</li>
<li><p>Payment option → No upfront / Partial / All upfront (Upfront will give you more discount but you can also opt for No upfront which falls on your normal billing cycle)</p>
</li>
<li><p>Lookback period → 7, 30, or 60 days (This is the sample peroid)</p>
</li>
</ul>
</li>
<li><p>Click on <strong>Run Analysis</strong></p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/7cb623ce-e158-4971-b81f-f2a78f1e2f4e.png" alt="" style="display:block;margin:0 auto" />

<h3>Making the final decision</h3>
<p>With these insights, you can make a much more informed decision on choosing the right commitment.</p>
<p>And if you’re still unsure, you can always reach out to the <strong>AWS Support team</strong> they can help you arrive at a number that fits your usage pattern.  </p>
<h1><strong>Conclusion</strong></h1>
<p>At the end of the day, just by purchasing a Savings Plan and committing to the <strong>right $/hour value</strong>, you can easily save around <strong>10–15%</strong> on your overall bill (or even more, depending on the plan).</p>
<p>The best part?</p>
<p>👉 You don’t need to pay anything upfront<br />👉 You continue with your normal billing cycle<br />👉 You’re simply committing: <em>“I’ll use X $/hour for the next 1 year”</em></p>
<p>This makes it a great starting point for many teams especially since <strong>Compute Savings Plans come with minimal restrictions</strong>, so you don’t have to worry about how you provision your resources.</p>
<h3>What’s next?</h3>
<p>If you haven’t tried this yet in your organization, it’s definitely worth exploring you might be leaving easy savings on the table 💰</p>
<p>Since this blog is already getting long, in the next one we’ll dive into <strong>Reserved Instances</strong>, where you can get even better savings—but with a few more restrictions.</p>
]]></content:encoded></item><item><title><![CDATA[🔐 Kubernetes - Secrets]]></title><description><![CDATA[We’ve already seen how to use ConfigMaps to pass non-sensitive data like configs and files to Pods. If you missed that, you can check it out here.
But now comes the real question:

What about sensitiv]]></description><link>https://claybrainer.com/kubernetes-secrets</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-secrets</guid><category><![CDATA[kubernetes-secrets]]></category><category><![CDATA[secrets]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[volume mount]]></category><category><![CDATA[kubernetes  RBAC]]></category><category><![CDATA[k8-secret]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Wed, 01 Apr 2026 17:28:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/66e68206cfcc77da336c5fd8/c87f3ae9-d85f-4129-95a9-c395f75350ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We’ve already seen how to use ConfigMaps to pass <strong>non-sensitive data</strong> like configs and files to Pods. If you missed that, you can check it out <a href="https://claybrainer.com/kubernetes-configmaps">here</a>.</p>
<p>But now comes the real question:</p>
<ul>
<li><p><em>What about sensitive data?</em></p>
</li>
<li><p><em>Passwords? Tokens? SSH keys?</em></p>
</li>
</ul>
<p>That’s where <strong>Kubernetes Secrets</strong> come in. In this blog, we’ll cover:</p>
<ul>
<li><p>What are Secrets and why we need them</p>
</li>
<li><p>How to create Secrets</p>
</li>
<li><p>How Pods consume them</p>
</li>
<li><p>Lifecycle &amp; behavior</p>
</li>
<li><p>Security best practices (this is where most people mess up 😅)</p>
</li>
</ul>
<h1>🤔 What are Kubernetes Secrets?</h1>
<p>A <strong>Secret</strong> is a Kubernetes object used to store <strong>sensitive data</strong> like: <em>Passwords , API tokens, SSH keys , Certificates.</em></p>
<p>Instead of hardcoding these inside your app or YAML, you <strong>decouple them</strong> using Secrets.</p>
<p><strong>⚙️ How are they different from ConfigMaps?</strong></p>
<p>They look similar to ConfigMaps however they have some distinct behaviour</p>
<ul>
<li><p><strong>Stored in etcd</strong> (cluster DB)</p>
</li>
<li><p><strong>Mounted as tmpfs (RAM)</strong> → never written to disk 🧠</p>
</li>
<li><p><strong>Base64 encoded</strong> (⚠️ not encryption!)</p>
</li>
</ul>
<blockquote>
<p>💡 Important: Base64 ≠ Security. Anyone with access can decode it easily.</p>
</blockquote>
<h1>😌 Why do we need Secrets?</h1>
<p>You already know the answer, but let’s reinforce:</p>
<ul>
<li><p>Keeps sensitive data out of code</p>
</li>
<li><p>Helps follow <strong>12-factor app principles</strong></p>
</li>
<li><p>Enables secure configuration per environment</p>
</li>
</ul>
<p>You can think of it like this</p>
<ul>
<li><p>ConfigMap = app config</p>
</li>
<li><p>Secret = app credentials</p>
</li>
</ul>
<h1>🛠️ How to Create Secrets</h1>
<p>You can create Secrets in <strong>two ways</strong>:</p>
<ul>
<li><p>Inline Command</p>
</li>
<li><p>Using YAML</p>
</li>
</ul>
<p>Let's explore them in detail</p>
<h3>⚡ 1. Inline Command (Quick &amp; Dirty)</h3>
<p><strong>Example:</strong></p>
<pre><code class="language-shell"># Basic syntax
kubectl create secret &lt;type&gt; &lt;name&gt; --from-literal=&lt;key&gt;=&lt;value&gt;

# Example
kubectl create secret generic api-credentials \
  --from-literal=token=super-secret-123
</code></pre>
<p><strong>Multi-value example:</strong></p>
<pre><code class="language-shell">kubectl create secret docker-registry private-reg-auth \
  --docker-username=my-user \
  --docker-password=my-password \
  --docker-server=my-registry.io \
  --docker-email=my-email@example.com
</code></pre>
<h2>When to Choose this approach ?</h2>
<p>Use this for:</p>
<ul>
<li><p>Quick testing</p>
</li>
<li><p>PoCs</p>
</li>
</ul>
<blockquote>
<p>Note: Here you can directly pass the values without encoding, Kubernetes encode the value and store it in Etcd</p>
</blockquote>
<h2>📄 2. Using YAML (Production Way)</h2>
<pre><code class="language-yaml">apiVersion: v1
kind: Secret
metadata:
  name: my-db-secret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDNoNGo1aw==
</code></pre>
<p>Here you <strong>must encode values manually</strong> using <code>echo -n "admin" | base64</code></p>
<h2>🧩 Secret Types (Quick Understanding)</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Use Case</th>
</tr>
</thead>
<tbody><tr>
<td><code>Opaque</code></td>
<td>Default, anything</td>
</tr>
<tr>
<td><code>kubernetes.io/dockerconfigjson</code></td>
<td>Private registry auth</td>
</tr>
<tr>
<td><code>kubernetes.io/tls</code></td>
<td>TLS cert + key</td>
</tr>
<tr>
<td><code>kubernetes.io/basic-auth</code></td>
<td>Username/password</td>
</tr>
<tr>
<td><code>kubernetes.io/ssh-auth</code></td>
<td>SSH keys</td>
</tr>
<tr>
<td><code>service-account-token</code></td>
<td>Internal API access</td>
</tr>
</tbody></table>
<blockquote>
<p>💡 Tip: Use specific types when possible — Kubernetes validates structure.</p>
</blockquote>
<h1>🔌 How Pods Consume Secrets</h1>
<p>Two main ways (same as ConfigMaps 👀):</p>
<ul>
<li><p>Environment Variables</p>
</li>
<li><p>Volume Mounts</p>
</li>
</ul>
<h2>🛡️ Option 1: Environment Variables</h2>
<p>This option is suitbale for for API keys or database passwords that the application expects to find in its environment. This is simple and clean approach</p>
<p><strong>Example</strong></p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
spec:
  containers:
    - name: my-app
      image: nginx
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-creds
              key: password
</code></pre>
<p>⚠️ Cons:</p>
<ul>
<li><p>Values are loaded <strong>only at startup</strong></p>
</li>
<li><p>If Secret changes → <strong>Pod restart required</strong> 🔄</p>
</li>
</ul>
<h3>📁 Option 2: Volume Mounts (Recommended)</h3>
<p>This option is best for certificates, SSH keys, or large configuration files. Kubernetes mounts the secret as a directory where each key in the secret becomes a file.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: secret-volume-pod
spec:
    containers:
    - name: ssh-container
      image: nginx 
      volumeMounts:
        - name: ssh-key-vol 
          mountPath: "/etc/ssh-keys" 
          readOnly: true 
volumes: 
    - name: ssh-key-vol 
      secret: 
        secretName: ssh-key-secret # The name of the Secret object
</code></pre>
<p>👉 Each key becomes a file:</p>
<pre><code class="language-plaintext">/etc/ssh-keys/
  ├── private_key
  ├── public_key
</code></pre>
<h3>💡 Why use Volumes over Environment Variables?</h3>
<ul>
<li><p><strong>Security</strong>: Secret volumes are stored in <strong>tmpfs</strong> (RAM). If the node is powered down, the data vanishes from the node's memory. 🧠</p>
</li>
<li><p><strong>Updates</strong>: If you update a Secret, Kubernetes automatically updates the files in the volume (though this takes a few seconds). Environment variables, however, are static—you would have to restart the Pod to see a change. 🔄</p>
</li>
<li><p><strong>Structure</strong>: Volumes are perfect for things that naturally exist as files, like SSL certificates or SSH <code>id_rsa</code> keys.</p>
</li>
</ul>
<h3>📁 Standard Mount vs. subPath</h3>
<h2>🔹 Standard Mount (Default Behavior)</h2>
<p>When you mount a Secret as a volume:</p>
<ul>
<li><p>The <strong>entire target directory gets replaced</strong></p>
</li>
<li><p>Any existing files become <strong>invisible</strong></p>
</li>
<li><p>Only the <strong>mounted Secret content is available for your pod to use.</strong></p>
</li>
</ul>
<p>👉 If you mount another Secret to the same path, it will <strong>overwrite the previous one</strong></p>
<p>This behavior is intentional in Kubernetes to Ensure <strong>data isolation,</strong> Improve <strong>security,</strong> Avoid unintended file conflicts</p>
<p>Now consider a scenario where you want to mount <strong>multiple Secrets into the same Pod path</strong>.</p>
<p>Using the standard approach:</p>
<ul>
<li><p>Mounting <strong>Secret A</strong> → works fine</p>
</li>
<li><p>Mounting <strong>Secret B to the same path</strong> → <strong>overwrites Secret A</strong></p>
</li>
</ul>
<p>👉 Result: The first Secret becomes inaccessible.</p>
<p>To solve this, Kubernetes provides a feature called <code>subPath</code>.</p>
<h3>💡 What is <code>subPath</code>?</h3>
<p><code>subPath</code> allows you to:</p>
<ul>
<li><p>Mount <strong>individual files or subdirectories</strong></p>
</li>
<li><p>Place them <strong>inside an existing directory</strong></p>
</li>
<li><p>Avoid overriding the entire volume path</p>
</li>
</ul>
<blockquote>
<p>To know in detail about subpath <a href="https://claybrainer.com/kubernetes-configmaps#subpath">check here</a></p>
</blockquote>
<p>✅ Using <code>subPath</code> for Multiple Secrets</p>
<p>With <code>subPath</code>, you can mount multiple Secrets into the same directory <strong>without conflicts</strong>.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-yaml">volumeMounts:
  - name: secret-a
    mountPath: /etc/config/secret-a.txt
    subPath: secret-a.txt
  - name: secret-b
    mountPath: /etc/config/secret-b.txt
    subPath: secret-b.txt
volumes:
  - name: secret-a
    secret:
      secretName: secret-a
  - name: secret-b
    secret:
      secretName: secret-b
</code></pre>
<h3>🔍 What happens here?</h3>
<ul>
<li><p><code>secret-a</code> is mounted as <code>/etc/config/secret-a.txt</code></p>
</li>
<li><p><code>secret-b</code> is mounted as <code>/etc/config/secret-b.txt</code></p>
</li>
<li><p>No overwriting occurs</p>
</li>
<li><p>Both Secrets coexist in the same directory 🎉</p>
</li>
</ul>
<p>One thing to note here is that when using <code>subPath</code> we have to define path with filename and extension whereas in standardMount the mount path is sufficient enough.</p>
<blockquote>
<p>LifeCycle for <code>subPath</code><br />One more important thing is that when using <code>subPath</code> when the secret value is updated it will not update the file directly pod restart is required because here the file is a static bind-mount</p>
</blockquote>
<h1>🔄 Lifecycle of Secrets</h1>
<p>In this section we will explore how changing a secret value will be handled in different Consumption option</p>
<table>
<thead>
<tr>
<th>Consumption Type</th>
<th>Behavior</th>
</tr>
</thead>
<tbody><tr>
<td>Env Variables</td>
<td>Deployment Requires restart 🔁</td>
</tr>
<tr>
<td>Volume Mount</td>
<td>Auto updates ⏱️</td>
</tr>
<tr>
<td>subPath</td>
<td>Deployment Requires restart 🔁</td>
</tr>
</tbody></table>
<blockquote>
<p>Even though VolumeMount Auto Updates your secrets to the Mount file you app should be configured to pick the updated value.</p>
</blockquote>
<h1>Security Best Practices &amp; RBAC</h1>
<p>Now that we know how to create and consume Secrets, Let's talk about how to actually keep them "secret."</p>
<p>Even though Secrets are better than plain-text ConfigMaps, they have some vulnerabilities by default:</p>
<ul>
<li><p><strong>Base64 is not encryption</strong>: It's just a way to store binary data as text. Anyone who can run <code>kubectl get secret -o yaml</code> can see your passwords. 🔓</p>
</li>
<li><p><strong>Etcd Storage</strong>: By default, Secrets are stored in plain text in the cluster's database (<code>etcd</code>).</p>
</li>
</ul>
<h2>Some of the best practices</h2>
<h3>Encryption at Rest (The Native Way)</h3>
<p>You can configure the Kubernetes API server with an EncryptionConfiguration file. When this is enabled, the API server will mathematically encrypt the Secret data before it saves it to etcd. Even if someone steals the etcd hard drive, the data is unreadable garbage without the encryption key.</p>
<h3>Strict RBAC (Role-Based Access Control)</h3>
<p>As we discussed briefly before, you must restrict who can run kubectl get secrets. If a developer has the get or list permission for Secrets, they can just ask the API server to show them the Base64 data, bypassing all etcd encryption. You should only grant Secret access to the specific ServiceAccounts that the Pods use, not to human users.</p>
<p>To truly secure a Secret while still letting it be used:</p>
<ul>
<li><p>For Users (Developers): You generally want to deny get, list, and watch. If they can't get it, they can't see the password.</p>
</li>
<li><p>For Pods: The Pod doesn't actually need the user to have permissions. The Pod uses a ServiceAccount. The Kubernetes controller-manager handles the mounting process, so as long as the Pod's YAML is allowed to be created, the system handles the rest.</p>
</li>
</ul>
<h3>External Secret Stores (The Enterprise Way)</h3>
<p>In large companies, they don't even store the source-of-truth secrets in Kubernetes at all! They use tools like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault. They use plugins (like the Secrets Store CSI Driver or External Secrets Operator) to fetch the password from the cloud vault and inject it directly into the Pod at runtime.  </p>
<h1>Conclusion</h1>
<p>Kubernetes Secrets might look simple at first, but how you use them makes all the difference. They help you keep sensitive data out of your code and configs—but they are <strong>not secure by default</strong>.</p>
<p>If you use them the right way prefer volumes over env vars, restrict access with RBAC, and integrate with external secret managers—you move from just “working setup” to a <strong>production-ready secure system</strong> 🔐</p>
<p>Thanks for reading. Keep learning, keep building, and keep breaking things (that’s how we grow) . See you in the next one ✌️</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes - ConfigMaps]]></title><description><![CDATA[We have already explored Volumes in Kubernetes in the previous blog. We understood how pods can store and share data using volumes.
But let’s pause and think for a moment 🤔

What if we just want to p]]></description><link>https://claybrainer.com/kubernetes-configmaps</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-configmaps</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[#kubernetes #container ]]></category><category><![CDATA[configmap]]></category><category><![CDATA[kubernetes configmaps and secrets]]></category><category><![CDATA[#Mastering ConfigMaps]]></category><category><![CDATA[config-maps]]></category><category><![CDATA[kubernetes-configmap]]></category><category><![CDATA[Devops]]></category><category><![CDATA[cloud native]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sat, 21 Feb 2026 20:44:05 GMT</pubDate><enclosure url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/66e68206cfcc77da336c5fd8/631286ff-ec35-4d8f-adfd-818c1ea26aa3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We have already explored <a href="https://claybrainer.com/kubernetes-volumes-part-1"><strong>Volumes in Kubernetes</strong></a> in the previous blog. We understood how pods can store and share data using volumes.</p>
<p>But let’s pause and think for a moment 🤔</p>
<ul>
<li><p>What if we just want to pass <strong>configuration values</strong> to a Pod? For example:</p>
<ul>
<li><p>Database URL</p>
</li>
<li><p>Application mode (dev / prod)</p>
</li>
<li><p>Feature flags</p>
</li>
<li><p>External service endpoints</p>
</li>
</ul>
</li>
</ul>
<p>We don’t want to hardcode these values inside the container image. Because configuration changes frequently, but images should remain stable. So how do we handle this in Kubernetes? 👉 This is exactly where <strong>ConfigMaps</strong> come into the picture.</p>
<p>In this blog we will explore</p>
<ul>
<li><p>What is ConfigMaps</p>
</li>
<li><p>How and Where to use it</p>
</li>
<li><p><strong>Creation Methods</strong>: Understanding the various ways to build ConfigMaps using <code>kubectl</code> literals, files, or YAML manifests.</p>
</li>
<li><p><strong>Consumption Strategies</strong>: Discovering how Pods actually "read" this data, whether through environment variables or by mounting them as files.</p>
</li>
<li><p><strong>Lifecycle &amp; Updates</strong>: Exploring what happens when configuration changes and how to handle updates without breaking your services.</p>
</li>
</ul>
<h1>What is a ConfigMap? 📦</h1>
<p>A <strong>ConfigMap</strong> is a Kubernetes Object is used to store <strong>non-sensitive configuration data</strong> as key-value pairs. It allows you to:</p>
<ul>
<li><p>Decouple configuration from container images. This means you can use the exact same image in development, staging, and production, simply by swapping out the ConfigMap associated with it.</p>
</li>
<li><p>Pass environment variables to pods</p>
</li>
<li><p>Provide configuration files to containers</p>
</li>
<li><p>Update configs without rebuilding images</p>
</li>
</ul>
<blockquote>
<p>In simple words:</p>
<p>ConfigMap = A way to inject configuration into your Pod dynamically.</p>
</blockquote>
<h1>How ConfigMap Works 🔍</h1>
<p>A ConfigMap stores configuration as:</p>
<ul>
<li><p>Key-value pairs</p>
</li>
<li><p>Entire configuration files (like an <code>nginx.conf</code> or <code>settings.py</code>)</p>
</li>
<li><p>Environment Variable</p>
</li>
</ul>
<p>Then Kubernetes allows you to: Inject them as <strong>environment variables</strong> or Mount them as <strong>files inside a container.</strong></p>
<blockquote>
<p>Remember that Configmaps are not intended for sensitive information like passwords or API keys—for those, we use <strong>Secrets</strong></p>
</blockquote>
<h2>Complete Internal Flow Summary:</h2>
<ul>
<li><p>kubectl sends ConfigMap definition to API Server</p>
</li>
<li><p>API Server validates and stores ConfigMap in etcd</p>
</li>
<li><p>ConfigMap now exists as a cluster object</p>
</li>
<li><p>Pod referencing ConfigMap is scheduled to a node</p>
</li>
<li><p>kubelet detects Pod and fetches ConfigMap</p>
</li>
<li><p>kubelet injects ConfigMap into container (env or volume)</p>
</li>
<li><p>Container starts</p>
</li>
<li><p>Application consumes configuration</p>
</li>
</ul>
<h1>ConfigMap - Creation Methods</h1>
<h2>Method 1: Literal Key-Value Pairs 🔑</h2>
<p>The first method is using <code>kubectl</code> command and creating a config map using <code>literals</code> directly from terminal</p>
<pre><code class="language-shell"># Syntax
kubectl create configmap &lt;map-name&gt; --from-literal=&lt;key&gt;=&lt;value&gt; 

# Example
kubectl create configmap app-settings --from-literal=ui_color=blue --from-literal=debug_mode=true
</code></pre>
<h3>When to use this method:</h3>
<p>You should use <strong>Literal Key-Value Pairs</strong> when quick testing/debugging is needed. If you want to:</p>
<ul>
<li><p>Quickly toggle a feature flag</p>
</li>
<li><p>Change a log level</p>
</li>
<li><p>Test a temporary configuration</p>
</li>
<li><p>Small, Simple Configuration</p>
</li>
</ul>
<p>While literals are great for quick flags, real-world applications often have dozens of settings stored in configuration files (like <code>.properties</code>, <code>.env</code>, or <code>.yaml</code>). Kubernetes allows you to take an entire file and shove it into a ConfigMap in one go.</p>
<h2>Method 2: From a File 📄</h2>
<p>In this method instead of passing individual Key Value pairs we can pass the whole file as an input. This is very useful when we have a need to pass a config file to the Container image.</p>
<p>When you use the <code>--from-file</code> flag, Kubernetes uses the <strong>filename</strong> as the key and the <strong>file contents</strong> as the value.</p>
<p>Imagine you have a file named <code>connection.conf</code> with this content:</p>
<pre><code class="language-plaintext"># Filename: connection.conf

db_host=prod-db.example.com
db_port=5432
</code></pre>
<p>To configure this file in Config map we will use below command</p>
<p><code>kubectl create configmap db-config --from-file=connection.conf</code></p>
<p>Inside the ConfigMap, it looks like this:</p>
<ul>
<li><p><strong>Key</strong>: <code>connection.conf</code></p>
</li>
<li><p><strong>Value</strong>: (The entire multiline string of the file)</p>
</li>
</ul>
<h3>When to use this Method:</h3>
<p>This is similar to the above literals the only difference is that here we can pass the whole file. This method is also useful for short term testing or for temporary change</p>
<h2>Method 3: Declarative YAML 📝</h2>
<p>This is the widely used industry standard method for declaring ConfigMaps. All the ConfigMaps are written in a YAML file in declarative way and then we apply it. This gives us an option to maintain desired state and also help us to follow GitOps. This allows us to track changes in Git (Infrastructure as Code).</p>
<p>A ConfigMap in YAML looks like this:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config
data:
  # Simple key-value pair
  log_level: "INFO"
  DB_TYPE: "postgres"
  # Multiline configuration file
  nginx.conf: |
    server {
      listen 80;
      server_name localhost;
    }
</code></pre>
<p>To apply this to our K8 cluster we use below default apply command</p>
<p><code>kubectl apply -f &lt;filename&gt;</code></p>
<h3>When to use this Method:</h3>
<p>This is the most commonly used best practice method. Apart from short term testing it is recommended to use Declarative YAML method while creationg configmaps.</p>
<p>We understood the methods of creating config maps now lets explore how we can consume it.</p>
<h1>ConfigMap - Consumption Strategies</h1>
<p>Whenever you create a configmap using any of the above method the configmaps are stored in the cluster now your application needs to read it. There are two methods to consume the configmaps</p>
<ul>
<li><p><strong>Environment Variables</strong>: Good for individual settings (like <code>DB_TYPE</code>).</p>
</li>
<li><p><strong>Volume Mounts</strong>: Good for configuration files (like <code>nginx.conf</code>).</p>
</li>
</ul>
<h2>Method 1: <strong>Environment Variables</strong>:</h2>
<p>In this method we will fecth the ConfigMap and use it as an Environment variable in the pod.</p>
<p>Let's take the below Pod Definition example</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: sample-app-pod
spec:
  containers:
    - name: sample-container
      image: nginx:latest
      env:
        - name: DATABASE_KIND   # Environment variable inside container
          valueFrom:
            configMapKeyRef:
              name: test-config   # ConfigMap name
              key: DB_TYPE         # Key inside ConfigMap
</code></pre>
<h3>When to use this Method:</h3>
<p>When your config maps is just are just Key Value pair and your applications can consume it via environment variable you can use this method.</p>
<h2>Method 2: <strong>Volume Mounts</strong>:</h2>
<p>In this method we will mount the ConfigMaps as a volume. So the keys in the configMap will be file name in the Volume and value will be content inside the file.</p>
<p>Example:</p>
<pre><code class="language-yaml"># weather-config.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: weather-config
data:
  city: "Chennai"
  temperature-unit: "Celsius"
  app.properties: |
    forecast.days=5
    refresh.interval=30
</code></pre>
<pre><code class="language-yaml"># weather-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: weather-app-pod
spec:
  containers:
    - name: my-container
      image: nginx:latest
      volumeMounts:
        - name: config-dir              # Must match volume name below
          mountPath: /etc/config        # Files appear here inside container
          readOnly: true
  volumes:
    - name: config-dir
      configMap:
        name: weather-config            # ConfigMap name
</code></pre>
<h3>How the config maps are stored in this method ?</h3>
<p>Kubernetes will read the <code>weather-config</code> ConfigMap, convert each key into a file, mount those files inside <code>/etc/config</code></p>
<p>Inside your container you will be seing the Config map as file like this : <code>/etc/config/city</code> , <code>/etc/config/temperature-unit</code></p>
<blockquote>
<p>Everything is a "File" in a Volume:</p>
<p><strong>[CREATION]:</strong> Whether you create a ConfigMap from a literal (<code>--from-literal</code>) or a file (<code>--from-file</code>) or using manifest files, Kubernetes stores them the exact same way key-value pairs</p>
<p><strong>[CONSUMPTION]:</strong> But, when you mount a ConfigMap as a volume, Kubernetes treats every <strong>key</strong> as a filename and every <strong>value</strong> as the content of that file. It doesn't care if the original source was a literal string or a 500-line config file.</p>
<p>Remember the difference between creating a config maps(which we discussed in previous section) and consuming the configmaps(which we are discussing now). This above point is when you consume configMaps as volume</p>
</blockquote>
<p>There is a small catch here 👀. Whenever you mount a <strong>ConfigMap as a volume</strong>, Kubernetes mounts it on the specified path inside the container.</p>
<blockquote>
<p>👉 But here is the important part:</p>
<p><em>When a volume is mounted to a path, any existing content in that path will be hidden.</em> Only the data from the <strong>latest mounted volume</strong> will be visible.</p>
</blockquote>
<p><strong>🔎 Let’s Understand With Example</strong></p>
<p>Assume that you have two configMaps named <code>xyz-config</code> and <code>weather-config</code> . If you want to mount both in the same path <code>mountPath: /etc/config</code>, then</p>
<ul>
<li><p>The second mount <code>weather-config</code> will override the first one</p>
</li>
<li><p>Only the latest mounted config map will be visible</p>
</li>
<li><p>Other files in that path become unavailable to the pod</p>
</li>
</ul>
<p><strong>🤔 Why Is It Like This?</strong></p>
<p>This is Kubernetes’ default behavior. When you mount a volume to a path,</p>
<ul>
<li>Kubernetes overlays that volume on that directory. The original content is hidden. This ensures clean and predictable file mapping. Also improves isolation and avoids accidental file mixing (security + consistency reasons).</li>
</ul>
<p>So what is the solution if I want to mount all configMaps to the specific Path Or map my ConfigMap to a specific path and also use other contents from the path. This is where a new parameter comes in called <code>subPath</code></p>
<h3>subPath</h3>
<p>If you want to mount multiple ConfigMaps to the same directory without hiding each other, you can use <code>subPath</code></p>
<blockquote>
<p>👉 what <code>subPath</code> tells Kubernetes:</p>
<p>Mount this specific file inside the directory, not override the entire directory.</p>
</blockquote>
<h3>Example - without subPath</h3>
<pre><code class="language-yaml">volumeMounts:
  - name: xyz-volume
    mountPath: /etc/config   
  - name: weather-volume
    mountPath: /etc/config # This will override the previous one ❌
  
</code></pre>
<h3>Example - with subPath</h3>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: multi-config-pod
spec:
  containers:
    - name: my-container
      image: nginx:latest
      volumeMounts:
        - name: weather-volume
          mountPath: /etc/config/weather.properties
          subPath: weather.properties
          readOnly: true

        - name: xyz-volume
          mountPath: /etc/config/xyz.properties
          subPath: xyz.properties
          readOnly: true

  volumes:
    - name: weather-volume
      configMap:
        name: weather-config

    - name: xyz-volume
      configMap:
        name: xyz-config
</code></pre>
<blockquote>
<p><strong>🧠 Important Note</strong></p>
<ul>
<li><p>When using normal mount you only specify directory path. <code>mountPath: /etc/config</code> . This is because by default, when you mount a volume to a path, Kubernetes creates a <strong>directory</strong> at that path. If you mount it exactly at <code>/app/static/theme.css</code>, that path will become a directory, and your file would actually live at <code>/app/static/theme.css/theme.css</code>. <strong>This is why when you are not using subpath you have to define only the path without filename</strong></p>
</li>
<li><p>But when using <code>subPath</code>, You must specify the <strong>complete file path including filename</strong>. <code>mountPath: /etc/config/weather.properties.</code> This is because now you are mounting a specific file, not the whole directory.</p>
</li>
</ul>
</blockquote>
<h1>ConfigMap - Lifecycle &amp; Updates 🔄</h1>
<p>In this section, let’s understand something very practical:</p>
<p>👉 What happens when you update a ConfigMap?<br />👉 How does Kubernetes handle those updates?<br />👉 Does your application automatically see the changes?</p>
<p>The behavior actually depends on <strong>how you consume the ConfigMap</strong>. There are two main consumption strategies which we discussed, as <strong>Environment Variables and</strong> as <strong>Volume Mounts.</strong> Let's see what happens on both clearly</p>
<h2>🧪 Lifecycle of ConfigMap as Environment Variable</h2>
<p>When you consume a ConfigMap as an <strong>environment variable</strong>, and later you update the ConfigMap, <strong>The running Pods will NOT see the changes.</strong></p>
<p>🤔 Why is this? - Because, <strong>ConfigMap values are injected into the container only at startup.</strong> Once the container starts Environment variables are set. They become part of the container’s runtime. Kubernetes does NOT re-inject updated values.</p>
<p>So even you edit your config map using <code>kubectl edit configmap my-config</code> the values of ConfigMaps will be updated but your pods will not use the updated values.</p>
<p>✅ So What Should You Do? <strong>You must restart the Pod.</strong> There are multiple ways to do it</p>
<ul>
<li><p>Delete the Pod (ReplicaSet will recreate it)</p>
</li>
<li><p>Trigger a Deployment rollout</p>
</li>
</ul>
<p>Once the container restarts, the updated ConfigMap values will be injected.</p>
<h2>📂 Lifecycle of ConfigMap as Volume Mount</h2>
<p>Now this is interesting 👀</p>
<p>When you consume a ConfigMap as a <strong>Volume Mount</strong>, updates behave differently. When you update the ConfigMap, Kubernetes automatically updates the files inside the mounted volume. Yes — without restarting the Pod.</p>
<p>🤔 But there is a Catch? - The catch is in your application design.</p>
<p>Let's take a example, Your app reads <code>/etc/config/app.properties</code> , It reads it only once during startup. Now you update the ConfigMap. The file inside the container gets updated. But your app still uses old values. Because your application is not re-reading the file.</p>
<p>🧠 What Should Be Done? - If you consume ConfigMap via Volume, Your application should Watch the file for changes or periodically reload configuration or support dynamic configuration refresh Otherwise, the update exists at the file level — but not at the application level.</p>
<h2>🛡️ Immutable ConfigMaps</h2>
<p>Now let’s talk about something advanced and powerful.</p>
<p>Imagine, You have 300 Pods all are consuming the same ConfigMap via Volume Mount. They are watching for changes.</p>
<p>Whenever you update that ConfigMap, Kubernetes API server must notify all watchers, the control plane processes multiple update events, this increases API server load. It may cause unexpected behavior if files update mid-process</p>
<p>Now think practically, What if this configuration NEVER changes or changes very rarely ?</p>
<p>In such cases, Kubernetes gives you something called <strong>Immutable ConfigMaps</strong></p>
<p>You can mark a ConfigMap as immutable. This tells Kubernetes that the values in the configMaps will not change. So that Kubernetes will not watch these configMaps.</p>
<h3><strong>Example:</strong></h3>
<pre><code class="language-yaml">apiVersion: v1
kind: ConfigMap
metadata:
  name: static-config
immutable: true # This is the magic
data:
  api_key: "12345"
</code></pre>
<p>✅ Benefits of Immutability</p>
<ul>
<li><p><strong>Safety</strong>: Prevents accidental configuration changes that could break production.</p>
</li>
<li><p><strong>Performance</strong>: Kubernetes stops "watching" these files, reducing the load on the API server.</p>
</li>
</ul>
<h3>❓ But What If I Need to Change an Immutable ConfigMap?</h3>
<p>Good question 😉. Once a ConfigMap is marked as immutable, you CANNOT Modify the data, Remove the immutable flag, Edit it directly, Kubernetes strictly prevents this.</p>
<p>You could delete and recreate it, but that is risky. If a Pod restarts during the split second where the ConfigMap doesn’t exist Pod will fail to start.</p>
<p><strong>The Expert Way: Versioning (Rolling Forward Pattern):</strong></p>
<p>Instead of editing the old ConfigMap, create a new one</p>
<ul>
<li><p><strong>Create New:</strong> You create a new ConfigMap named <code>static-config-v2</code> with your updated database URL.</p>
</li>
<li><p><strong>Update Deployment:</strong> You edit your Deployment manifest to point to this new name.</p>
</li>
<li><p><strong>Rollout:</strong> Kubernetes sees the Deployment change and automatically performs a rolling update, creating new Pods with the new config and terminating the old ones safely.</p>
</li>
</ul>
<p>At this point, you might have one more doubt. If I’m using versioned ConfigMaps and I have 100 applications, do I need to manually update all 100 Deployment files every time the config changes?</p>
<p>Yes… technically you could. But that would be Painful 😅, Error Prone, Not Scalable. This is where tools like <strong>Kustomize</strong> come into play.</p>
<h3>⚙️ How Kustomize Solves This Problem</h3>
<p>Kustomize helps you manage Kubernetes configurations in a smarter way. When you use Kustomize to generate ConfigMaps:</p>
<ul>
<li><p>It automatically hashes the content.</p>
</li>
<li><p>It appends that hash to the ConfigMap name.</p>
</li>
</ul>
<p><strong>Example:</strong></p>
<ul>
<li>Instead of <code>static-config</code> it creates hashed value something like this <code>static-config-m9t8f5c92k</code></li>
</ul>
<p>We will explore Kustomize deeply in another blog (because that itself deserves a full discussion 😄).</p>
<h1>📋 Putting It All Together: The Expert Checklist</h1>
<p>Now we have explored ConfigMaps in detail:</p>
<ul>
<li><p>What they are</p>
</li>
<li><p>How they are consumed</p>
</li>
<li><p>Update behavior</p>
</li>
<li><p>Immutable pattern</p>
</li>
</ul>
<p>Let’s finish with a simple, practical checklist. Because in production, the real question is: When should I use what?</p>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p><strong>Feature</strong></p></th><th><p><strong>Environment Variables</strong></p></th><th><p><strong>Volume Mounts</strong></p></th></tr><tr><td><p><strong>Best For</strong></p></td><td><p>Small flags, single strings</p></td><td><p>Large config files, multi-line data</p></td></tr><tr><td><p><strong>Update Behavior</strong></p></td><td><p>Requires Pod restart</p></td><td><p>Updates automatically (eventually)</p></td></tr><tr><td><p><strong>App Logic</strong></p></td><td><p>Easy (<code>os.getenv</code>)</p></td><td><p>Medium (must watch file changes)</p></td></tr><tr><td><p><strong>Safety</strong></p></td><td><p>High (static during runtime)</p></td><td><p>Lower (files can change mid-process)</p></td></tr><tr><td><p><strong>Performance Impact</strong></p></td><td><p>Minimal</p></td><td><p>API server watches (unless immutable)</p></td></tr><tr><td><p><strong>Production Strategy</strong></p></td><td><p>Simple apps</p></td><td><p>Dynamic / reloadable systems</p></td></tr></tbody></table>

<h1>Common Doubts</h1>
<h2>If My Config Never Changes, Why Use ConfigMap At All why do we need Immutable?</h2>
<p>At first glance, this seems logical as I also had the same doubt. If it never changes, why not just hardcode it inside the application?</p>
<p>But here’s why ConfigMaps still matter.</p>
<h3>Separation:</h3>
<p>Even if configuration never changes, we still separate <strong>Application Code and Configuration,</strong> why because Same image can run in Dev, QA, Prod. Only configuration changes between environments. ou don’t rebuild image for every small config variation</p>
<p>So Even if production config rarely changes, it is still environment-specific. That separation is the core design principle.</p>
<h3>Image Immutability Principle:</h3>
<p>In Kubernetes world, Container images should be immutable. If we bake config inside the image any changes to the config maps will force us to rebuild the image which increment the image version for small change.</p>
<h1><strong>practical list of common ConfigMap-related errors</strong></h1>
<h3>ConfigMap Not Found Errors:</h3>
<p><strong>Error</strong>: <code>configmap "&lt;Configmap name&gt;" not found</code></p>
<p><strong>Why it happens:</strong> Typo in name, Wrong namespace, ConfigMap not created before Pod, Deleted accidentally</p>
<p><strong>Fix:</strong> Verify namespace: <code>kubectl get configmap -n &lt;ns&gt;</code> , Ensure order: ConfigMap must exist before Pod starts</p>
<h3>Wrong Namespace Issue:</h3>
<p><strong>Why it happens:</strong> Very common mistake. ConfigMap is created in default namespace as we forget to mention the namespace</p>
<p><strong>Fix:</strong> Create ConfigMap in the same namespace as Pod.</p>
<h3>Key Not Found Error</h3>
<p><strong>Error</strong>: <code>couldn't find key DB_HOST in ConfigMap</code></p>
<p><strong>Why it happens:</strong> Wrong key name, Case mismatch (<code>db_host</code> vs <code>DB_HOST</code>), Key deleted during update</p>
<p><strong>Fix:</strong> Make sure you are using same KEY across the namespace. Validate it with Configmap when you are consuming it</p>
<h3>Pod Stuck in CreateContainerConfigError</h3>
<p><strong>Error</strong>: <code>CreateContainerConfigError</code></p>
<p><strong>Why it happens:</strong> Missing ConfigMap, Missing key, Invalid reference. This usually means Kubernetes cannot inject the ConfigMap.</p>
<p><strong>Fix:</strong> Validate the configmap in correct namespace and the names are matched across the deployments. Validate Configmap is created or not</p>
<h3>Environment Variable Not Updating</h3>
<p><strong>Error</strong>: You update ConfigMap. But app still shows old value.</p>
<p><strong>Why it happens:</strong> Env variables are injected only at container startup.</p>
<p><strong>Fix</strong>: Restart Pod - <code>kubectl rollout restart deployment app</code></p>
<h3>Volume Mount Not Updating Immediately</h3>
<p><strong>Error</strong>: You update ConfigMap. File inside container still shows old content for some time.</p>
<p><strong>Why it happens:</strong> Update propagation delay, Kubelet sync period (usually ~1 min)</p>
<p><strong>Fix:</strong> This is normal behavior. No fix needed</p>
<h3>Application Not Picking Updated File</h3>
<p><strong>Error</strong>: File updated but app still behaves same.</p>
<p><strong>Why it happens:</strong> Application reads config only at startup. Kubernetes updated file but app never reloaded it.</p>
<p><strong>Fix:</strong> This is an <strong>application design issue</strong>, not Kubernetes issue. Need to design app to check config maps periodically or take changes whenever happens</p>
<h3>subPath Does NOT Auto-Refresh</h3>
<p><strong>Error:</strong> If we use <code>subPath</code> then configMap does not auto refresh</p>
<p><strong>Why it happens:</strong> Because subPath mounts a single file copy not the live symlink.</p>
<h3>Accidentally Overriding Directory</h3>
<p><strong>Error:</strong> When you mount configmap as Volume it hides other files in the volume</p>
<p><strong>Why it happens:</strong> This is the default behaviour of Kubernetes to have clean mount and saftey.</p>
<p><strong>Fix:</strong> If you want to mount the configMap where you also needs to use other files from volume use <code>subPath</code> to mount configmap.</p>
<h3><strong>Large ConfigMap Size Limit</strong></h3>
<p><strong>Error:</strong> etcd request too large</p>
<p><strong>Why it happens:</strong> ConfigMap size limit: ~1MB.</p>
<p><strong>Fix:</strong> Makesure you manage size of configmap in right way. ConfigMaps are not meant for Huge Binaries or Large Json datasets</p>
<h3>Watch Load on API Server</h3>
<p><strong>Error:</strong> API server getting overloaded</p>
<p><strong>Why it happens:</strong> Hundreds of Pods watching a frequently updated ConfigMap: High API server load, Performance degradation</p>
<p><strong>Fix:</strong> Use immutable configmap for static configs</p>
<h3>🧠 Production-Level Issues (Advanced)</h3>
<p>These are not syntax errors but architectural mistakes:</p>
<ul>
<li><p>Using ConfigMap for dynamic runtime feature toggles (bad design)</p>
</li>
<li><p>Updating ConfigMap frequently in high-scale clusters</p>
</li>
<li><p>Not versioning production config</p>
</li>
<li><p>Editing ConfigMap directly in production (breaks GitOps)</p>
</li>
<li><p>Using subPath when expecting dynamic updates</p>
</li>
</ul>
<h1>Conclusion</h1>
<p>ConfigMaps are a simple, powerful Kubernetes primitive for decoupling non-sensitive configuration from container images. They let you store key-value configuration outside of your images and supply those values to Pods either as environment variables or as mounted files — giving you flexibility to use the same image across environments and change configuration independently.</p>
<p>Key takeaways:</p>
<ul>
<li><p>Use ConfigMaps for non-sensitive, environment-specific values (use Secrets for sensitive data).</p>
</li>
<li><p>Create ConfigMaps via <code>kubectl</code> literals, files, or declarative YAML manifests depending on your workflow and need for version control.</p>
</li>
<li><p>Consume ConfigMaps as environment variables for small, simple values or mount them as files when applications expect file-based configs.</p>
</li>
<li><p>Be mindful of update behavior: mounted files are updated automatically (with caveats), while env-based values require Pod restart; manage updates via rollout restarts, immutable ConfigMaps, or reloader sidecars/controllers when appropriate.</p>
</li>
<li><p>Follow best practices: keep configs declarative (GitOps), name and label ConfigMaps clearly, avoid embedding large binaries, and consider using immutable ConfigMaps for stable deployments.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Block Storage vs File Storage: The RWX Truth]]></title><description><![CDATA[RWO vs RWX in Kubernetes — The Real Difference Between Block Storage and FileStorage
If you’ve worked with Kubernetes volumes, you’ve probably seen this statement:

“EBS (or any block storage) only supports RWO (ReadWriteOnce).For RWX (ReadWriteMany)...]]></description><link>https://claybrainer.com/block-storage-vs-file-storage-the-rwx-truth</link><guid isPermaLink="true">https://claybrainer.com/block-storage-vs-file-storage-the-rwx-truth</guid><category><![CDATA[files-storage]]></category><category><![CDATA[kubernetes-volume]]></category><category><![CDATA[ebs]]></category><category><![CDATA[EFS]]></category><category><![CDATA[block storage]]></category><category><![CDATA[file storage]]></category><category><![CDATA[ aws ebs vs efs]]></category><category><![CDATA[Kubernetes Storage]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Thu, 12 Feb 2026 17:20:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770916618259/4e991219-9277-445d-a3b9-7980ef2cc855.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-rwo-vs-rwx-in-kubernetes-the-real-difference-between-block-storage-and-filestorage">RWO vs RWX in Kubernetes — The Real Difference Between Block Storage and FileStorage</h2>
<p>If you’ve worked with Kubernetes volumes, you’ve probably seen this statement:</p>
<blockquote>
<p>“EBS (or any block storage) only supports RWO (ReadWriteOnce).<br />For RWX (ReadWriteMany), you need something like EFS.”</p>
</blockquote>
<p>If you’ve ever wondered <strong>why that is</strong>, you’re not alone. I had the same question when I first learned it. Let’s break it down properly.</p>
<h1 id="heading-before-the-crux-lets-get-the-basics-right">Before the Crux — Let’s Get the Basics Right</h1>
<h2 id="heading-what-is-block-storage">What Is Block Storage?</h2>
<p>Block storage is essentially a <strong>raw disk</strong>. When you create an EBS volume, Kubernetes (or the operating system) sees it as:</p>
<blockquote>
<p>“Here’s an empty hard drive. You decide how to use it.”</p>
</blockquote>
<p>Since it’s raw, it must be <strong>formatted with a filesystem</strong> (like ext4 or xfs) before use.</p>
<h2 id="heading-formatting-what-is-it">Formatting what is it ?</h2>
<p>Formatting means creating a <strong>filesystem structure</strong> on the disk. Think of it like this:</p>
<ul>
<li><p>You have a blank notebook (raw disk)</p>
</li>
<li><p>You draw structured grids inside it. Think of it like your Microsoft Excel</p>
</li>
<li><p>Now you can store and retrieve values in specific locations by mapping the Row and Column.</p>
</li>
</ul>
<p>In the same way the disk gets divided into <strong>blocks</strong>, and the filesystem keeps track of:</p>
<ul>
<li><p>Which block belongs to which file</p>
</li>
<li><p>Where data is stored</p>
</li>
<li><p>How to retrieve it quickly</p>
</li>
</ul>
<h2 id="heading-do-you-know-the-block-storage-have-fast-io-you-know-why">Do you know the Block Storage have Fast I/O. You know why ?'</h2>
<p>Because block storage allows <strong>direct access to data blocks</strong>. When an application wants data, the operating system can say: <code>“Give me block #504.”</code> it retrieve it directly from the Block storage Disk nothing between the storage and the Operating System.</p>
<p>That’s why block storage is:</p>
<ul>
<li><p>Low latency</p>
</li>
<li><p>High performance</p>
</li>
<li><p>Ideal for databases</p>
</li>
</ul>
<h2 id="heading-then-why-cant-block-storage-support-rwx">Then Why Can’t Block Storage Support RWX?</h2>
<p>A traditional filesystem (like ext4 or xfs) is not cluster-aware. If you attach the same block disk to multiple nodes and both try to write:</p>
<ul>
<li><p>Filesystem corruption can occur</p>
</li>
<li><p>There’s no built-in distributed locking (So that when one node is using it it locks other node)</p>
</li>
<li><p>Metadata can conflict (If some node tries to write and other tries to modify the metadata conflict occurs.)</p>
<ul>
<li>Metadata is nothing but the information about the data stored such as when it is created what is the size etc.</li>
</ul>
</li>
</ul>
<p>Block storage assumes: <strong><em>“One disk → one machine managing the filesystem.”</em></strong></p>
<p>That’s why:</p>
<ul>
<li><p>EBS supports <strong>ReadWriteOnce (RWO)</strong></p>
</li>
<li><p>It cannot safely support <strong>ReadWriteMany (RWX)</strong></p>
</li>
</ul>
<hr />
<h1 id="heading-what-is-file-storage">What Is File Storage?</h1>
<p>File storage (like EFS) is different. It is</p>
<ul>
<li><p>Already formatted</p>
</li>
<li><p>Managed by a <strong>file server</strong></p>
</li>
<li><p>Exposed over the network (NFS in EFS case)</p>
</li>
</ul>
<p>Instead of giving you raw blocks, it gives you <strong><em>A shared file system accessible over the network.</em></strong></p>
<h2 id="heading-you-may-ask-how-is-file-storage-different">You may ask How Is File Storage Different ?</h2>
<p>Even file storage uses blocks internally but the key difference is:</p>
<ul>
<li><p>A <strong>central file server manages metadata</strong></p>
</li>
<li><p>It handles locking</p>
</li>
<li><p>It coordinates concurrent access</p>
</li>
<li><p>It ensures consistency</p>
</li>
</ul>
<p>Every file contains metadata like:</p>
<ul>
<li><p>Permissions</p>
</li>
<li><p>Ownership</p>
</li>
<li><p>Size</p>
</li>
<li><p>Timestamps</p>
</li>
<li><p>Access rules</p>
</li>
</ul>
<h2 id="heading-what-happens-when-multiple-nodes-try-to-connect">What happens when multiple nodes try to connect ?</h2>
<ul>
<li><p>They don’t manage blocks directly</p>
</li>
<li><p><strong>They talk to the file server</strong></p>
</li>
<li><p><strong>The file server manages read/write coordination</strong></p>
</li>
</ul>
<p>That’s what enables <strong>RWX</strong>.</p>
<h2 id="heading-is-file-storage-slower">Is File Storage Slower?</h2>
<p>Generally, yes — compared to block storage. Why?</p>
<p>Because:</p>
<ul>
<li><p>It involves network communication</p>
</li>
<li><p>Metadata validation happens</p>
</li>
<li><p>There’s coordination overhead</p>
</li>
</ul>
<p>But the trade-off is: <strong><em>You get safe, shared, multi-node access.</em></strong></p>
<hr />
<h2 id="heading-the-real-crux">The Real Crux</h2>
<h3 id="heading-block-storage">Block storage:</h3>
<ul>
<li><p>Attached to one node</p>
</li>
<li><p>Filesystem managed locally</p>
</li>
<li><p>No distributed locking</p>
</li>
<li><p>High performance</p>
</li>
<li><p>Supports RWO</p>
</li>
</ul>
<h3 id="heading-file-storage">File storage:</h3>
<ul>
<li><p>Managed by centralized file servers</p>
</li>
<li><p>Supports distributed locking</p>
</li>
<li><p>Safe concurrent access</p>
</li>
<li><p>Supports RWX</p>
</li>
<li><p>Slightly higher latency</p>
</li>
</ul>
<h2 id="heading-the-mental-model">The Mental Model</h2>
<p>Think of it like this:</p>
<h3 id="heading-block-storage-ebs">Block Storage (EBS)</h3>
<p>Like a USB drive plugged into one laptop. Only that laptop controls it.</p>
<h3 id="heading-file-storage-efs">File Storage (EFS)</h3>
<p>Like a shared Google Drive folder. Multiple machines can access it at the same time.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes - Volumes - Part-2]]></title><description><![CDATA[👋 The "Node-Lock" Problem
That "node-lock" is the biggest weakness of hostPath. If the node goes down or the Pod moves, the data is essentially "trapped" on the old node.
To solve this, Kubernetes uses a system that decouples the storage from the no...]]></description><link>https://claybrainer.com/kubernetes-volumes-part-2</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-volumes-part-2</guid><category><![CDATA[PVC ]]></category><category><![CDATA[pv]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubernetes-volume]]></category><category><![CDATA[types of kubernetes volumes]]></category><category><![CDATA[PersistentVolumes]]></category><category><![CDATA[persistent volume claim]]></category><category><![CDATA[storageclass]]></category><category><![CDATA[storage class]]></category><category><![CDATA[Kubernetes Storage]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Thu, 05 Feb 2026 12:03:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769954421258/7e68734a-d7cb-4149-9866-11367fad4438.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-node-lock-problem">👋 The "Node-Lock" Problem</h2>
<p>That "node-lock" is the biggest weakness of <code>hostPath</code>. If the node goes down or the Pod moves, the data is essentially "trapped" on the old node.</p>
<p>To solve this, Kubernetes uses a system that decouples the storage from the node entirely, often using network storage (like a cloud disk). This brings us to the <strong>"Gold Standard"</strong> of Kubernetes storage: <strong>PersistentVolumes (PV)</strong> and <strong>PersistentVolumeClaims (PVC)</strong>.</p>
<h1 id="heading-persistent-volumespv-and-persistent-volumes-claim-pvc"><strong>Persistent Volumes(PV) and Persistent Volumes Claim (PVC)</strong></h1>
<h2 id="heading-the-what">The What ?</h2>
<h3 id="heading-persistent-volumes-pv"><strong>Persistent Volumes (PV):</strong></h3>
<p>A <strong>Persistent Volume (PV)</strong> represents a piece of storage provisioned in the cluster often backed by <strong>network or cloud storage</strong> that continues to exist even if Pods are deleted or moved.</p>
<p>You can think of a PV as <strong>block storage or file storage attached to the Kubernetes cluster</strong>, where the cluster has full control to allocate this storage to incoming requests.</p>
<p>Since this storage lives <strong>outside the Kubernetes cluster environment</strong> (for example, cloud disks or network-attached storage), it is <strong>not affected by Pod restarts, node failures, or most Kubernetes-level failures</strong>.</p>
<h3 id="heading-persistent-volumes-claim-pvc"><strong>Persistent Volumes Claim (PVC):</strong></h3>
<p>A <strong>Persistent Volume Claim (PVC)</strong> is a request for storage made by an application running inside the Kubernetes cluster.</p>
<p>Instead of directly attaching a storage disk, a Pod asks for storage by specifying:</p>
<ul>
<li><p>How much space it needs</p>
</li>
<li><p>The type of access required (read/write)</p>
</li>
<li><p>The storage characteristics it expects</p>
</li>
</ul>
<p>Kubernetes then looks for a suitable <strong>Persistent Volume (PV)</strong> that matches this request and binds it to the PVC.</p>
<p>You can think of a PVC as an <strong>interface between your application and the actual storage</strong>, allowing Pods to use persistent storage without needing to know where or how it is physically provisioned.</p>
<h3 id="heading-why-pvc-request-is-needed">❓ Why PVC request is needed ?</h3>
<p>PVCs introduce a <strong>clean separation between applications and storage</strong>.</p>
<p>Different applications have very different storage needs:</p>
<ul>
<li><p>Databases need <strong>high I/O and low latency</strong></p>
</li>
<li><p>Logs or backups can work with <strong>lower-performance storage</strong></p>
</li>
</ul>
<p>Instead of hard-coding storage details into applications, a PVC allows the app to simply say: “I need this much storage, with this access pattern.” Kubernetes then fulfills that request by binding the PVC to an appropriate PV.</p>
<p>This approach keeps applications <strong>portable, flexible, and storage-agnostic</strong>, while allowing storage performance to be controlled through PVC requests.</p>
<p>Here is a quick look at how they connect:</p>
<ul>
<li><p><strong>Pod</strong> points to → <strong>PVC</strong></p>
</li>
<li><p><strong>PVC</strong> binds to → <strong>PV</strong></p>
</li>
<li><p><strong>PV</strong> points to → <strong>Actual Disk</strong></p>
</li>
</ul>
<p>Each layer has a clear responsibility, and none of them need to know the internal details of the others.</p>
<h2 id="heading-access-modes">🔐 Access Modes</h2>
<p>Both <strong>Persistent Volumes (PV)</strong> and <strong>Persistent Volume Claims (PVC)</strong> define something called <strong>access modes</strong>. Access modes describe <strong>how a volume can be accessed by Pods</strong> for example, whether it can be mounted as read-only, read-write, or shared across multiple nodes. The available access modes are explained below</p>
<ul>
<li><p><strong>ReadWriteOnce (RWO):</strong> The volume can be mounted as read-write by a <strong>single</strong> node. (Like a USB drive plugged into one laptop).</p>
</li>
<li><p><strong>ReadOnlyMany (ROX):</strong> The volume can be mounted read-only by <strong>many</strong> nodes. (Like a shared CD-ROM).</p>
</li>
<li><p><strong>ReadWriteMany (RWX):</strong> The volume can be mounted as read-write by <strong>many</strong> nodes. (Like a shared network folder/Dropbox).</p>
</li>
</ul>
<p>When you provision <strong>PV and PVC for a highly available application</strong> that runs across multiple nodes, choosing the <strong>right access mode</strong> becomes critical. For example, If the volume is <strong>read-only</strong> and shared across Pods <strong>across nodes</strong>, you can use <strong>ReadOnlyMany (ROX)</strong>, If the volume needs to be <strong>read and written</strong> by multiple Pods <strong>across nodes</strong>, you must use <strong>ReadWriteMany (RWX)</strong></p>
<blockquote>
<p>What does “Many” mean here?</p>
<p>“Many” refers to the <strong>number of nodes</strong>, not Pods.</p>
<ul>
<li><p><strong>RWO</strong> → Only <strong>one node</strong> can read/write to the volume at a time. i.e all the pods in the node can access this volume</p>
</li>
<li><p><strong>RWX</strong> → <strong>Multiple nodes</strong> can read/write to the same volume simultaneously. i.e pods across nodes can access the volume</p>
</li>
</ul>
</blockquote>
<p>An important detail to note here is a <strong>Persistent Volume (PV)</strong> can be configured with <strong>multiple access modes</strong>, indicating what it <strong>supports</strong>. A <strong>Persistent Volume Claim (PVC)</strong>, on the other hand, requests <strong>a specific access mode</strong> based on the application’s requirement. If the requested access mode in the PVC is <strong>supported by the PV</strong>, Kubernetes approves the claim and binds them together.</p>
<blockquote>
<h3 id="heading-key-takeaway">🧠 Key Takeaway</h3>
<p><strong>PV advertises what it can support, and PVC asks for what it needs.</strong> Binding happens only when both agree on the access mode.</p>
</blockquote>
<h2 id="heading-example-kubernetes-resource-definition-file">Example Kubernetes Resource Definition File</h2>
<h3 id="heading-persistent-volume-pv">Persistent Volume (PV)</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolume</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">app-pv</span>               <span class="hljs-comment"># Name of the PV</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">capacity:</span>
    <span class="hljs-attr">storage:</span> <span class="hljs-string">10Gi</span>            <span class="hljs-comment"># Size of the volume</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>          <span class="hljs-comment"># Supported access mode</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteMany</span>
  <span class="hljs-attr">persistentVolumeReclaimPolicy:</span> <span class="hljs-string">Retain</span>
  <span class="hljs-comment"># Retain data even if PVC is deleted</span>
  <span class="hljs-attr">storageClassName:</span> <span class="hljs-string">ebs-sc</span>   <span class="hljs-comment"># Must match PVC</span>
  <span class="hljs-attr">csi:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">ebs.csi.aws.com</span>  <span class="hljs-comment"># AWS EBS CSI driver</span>
    <span class="hljs-attr">volumeHandle:</span> <span class="hljs-string">vol-0abcd1234efgh5678</span>
    <span class="hljs-comment"># Actual EBS volume ID from AWS</span>
  <span class="hljs-attr">volumeMode:</span> <span class="hljs-string">Filesystem</span>    <span class="hljs-comment"># Mount as a filesystem</span>
</code></pre>
<h3 id="heading-persistent-volume-pv-1">Persistent Volume (PV)</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">PersistentVolumeClaim</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">app-pvc</span>              <span class="hljs-comment"># Name of the PVC</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">accessModes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">ReadWriteOnce</span>          <span class="hljs-comment"># Only one node can read/write</span>
  <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
      <span class="hljs-attr">storage:</span> <span class="hljs-string">10Gi</span>          <span class="hljs-comment"># Amount of storage requested</span>
  <span class="hljs-attr">storageClassName:</span> <span class="hljs-string">ebs-sc</span>   <span class="hljs-comment"># Use this StorageClass</span>
</code></pre>
<h3 id="heading-consuming-a-pvc-inside-a-pod">Consuming a PVC Inside a Pod</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">app-pod</span>                 <span class="hljs-comment"># Name of the Pod</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app-container</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx</span>              <span class="hljs-comment"># Sample container image</span>
      <span class="hljs-attr">volumeMounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app-storage</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/data</span>      <span class="hljs-comment"># Path inside the container</span>
          <span class="hljs-comment"># The PVC will be mounted here</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app-storage</span>
      <span class="hljs-attr">persistentVolumeClaim:</span>
        <span class="hljs-attr">claimName:</span> <span class="hljs-string">app-pvc</span>      <span class="hljs-comment"># Name of the PVC to use</span>
</code></pre>
<h1 id="heading-storageclasses-leveling-up-to-dynamic-provisioning"><strong>StorageClasses:</strong> Leveling Up to Dynamic Provisioning</h1>
<p>In the real world the admin don’t want to worry about creating 100’s of PV’s(Persistent Volumes) with different types and sizes. Here is where <strong>StorageClasses</strong> (SC) comes in</p>
<h2 id="heading-how-it-works">How it works</h2>
<ul>
<li><p>The administrator creates a <strong>StorageClass</strong> in the cluster</p>
</li>
<li><p>The <strong>StorageClass</strong> is granted permission to create cloud provider disks (such as <strong>AWS EBS</strong> or <strong>Azure Disk</strong>) by installing the appropriate <strong>CSI driver</strong> in the cluster.</p>
<ul>
<li>For example, in an <strong>Amazon EKS</strong> cluster, you install the <strong>EBS CSI driver</strong>, which enables Kubernetes to dynamically provision EBS volumes on demand whenever a PVC is created.</li>
</ul>
</li>
<li><p>The PVC specifies <strong>which StorageClass to use</strong></p>
</li>
<li><p>When the PVC is created, Kubernetes checks:</p>
<ul>
<li>Is there an existing PV that matches this request?</li>
</ul>
</li>
<li><p>If <strong>no matching PV exists</strong>, Kubernetes uses the StorageClass to <strong>dynamically create a new PV</strong>.</p>
</li>
<li><p>The newly created PV is <strong>bound to the PVC</strong>.</p>
</li>
<li><p>When a Pod references the PVC, the volume is <strong>attached and mounted</strong> into the Pod.</p>
</li>
</ul>
<p>For example:</p>
<ul>
<li><p>A Pod requests <strong>50Gi</strong></p>
</li>
<li><p>No existing PV matches</p>
</li>
<li><p>The StorageClass dynamically creates a new disk (EBS etc.)</p>
</li>
<li><p>The disk is attached to the Pod as a volume</p>
</li>
</ul>
<p>No manual PV creation needed 🎉</p>
<h1 id="heading-reclaim-policies">Reclaim Policies</h1>
<p>Now comes an important question. Your application stores data in a PV via a PVC and that data can live forever.</p>
<p>But what if:</p>
<ul>
<li><p>You delete the application?</p>
</li>
<li><p>You no longer need the data?</p>
</li>
<li><p>You recreate the app with fresh data?</p>
</li>
</ul>
<p>Continuously creating new volumes is <strong>not sustainable</strong>, especially when storage costs add up. This is where <strong>Reclaim Policies</strong> come into play.</p>
<p>Reclaim policies define <strong>what happens to a Persistent Volume after the PVC is deleted</strong> whether the data should be:</p>
<ul>
<li><p><strong>Retained:</strong> The PV is not deleted. It stays in "<strong>Released”</strong> state and will not be assigned to any new PVC request. An admin must manually clean up the data. This is the <strong>safest</strong> for production databases.</p>
</li>
<li><p><strong>Deleted:</strong> The PV and the actual physical disk (AWS EBS, GCE PD) are deleted immediately. <strong>Should be choosen with extra caution as the data will be wiped once PVC is deleted</strong></p>
</li>
<li><p><strong>Recycle(Deprecated):</strong> It performs a basic <code>rm -rf /thevolume/*</code> and makes the PV available again. (Rarely used now).</p>
</li>
</ul>
<p>You control this with the <code>persistentVolumeReclaimPolicy</code></p>
<h2 id="heading-the-binding-process">The Binding Process</h2>
<p>Kubernetes matches a PVC to a PV using these criteria:</p>
<ol>
<li><p><strong>Capacity:</strong> Does the PV have at least the amount requested?</p>
</li>
<li><p><strong>Access Mode:</strong> Does the PV support the mode (RWO, RWX, etc.)?</p>
</li>
<li><p><strong>StorageClass:</strong> Do they have the same StorageClass name?</p>
</li>
<li><p><strong>Selectors:</strong> (Optional) Do the labels match?</p>
</li>
</ol>
<h2 id="heading-example-kubernetes-resource-definition-file-1">Example Kubernetes Resource Definition File</h2>
<h3 id="heading-storageclass-dynamic-provisioning">StorageClass (Dynamic Provisioning)</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">storage.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">StorageClass</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">ebs-sc</span>               <span class="hljs-comment"># Name of the StorageClass</span>
<span class="hljs-attr">provisioner:</span> <span class="hljs-string">ebs.csi.aws.com</span> <span class="hljs-comment"># CSI driver that talks to AWS EBS</span>
<span class="hljs-attr">parameters:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">gp3</span>                  <span class="hljs-comment"># EBS volume type (gp2 / gp3 / io1, etc.)</span>
<span class="hljs-attr">reclaimPolicy:</span> <span class="hljs-string">Delete</span>        <span class="hljs-comment"># What happens to the disk when PVC is deleted</span>
<span class="hljs-attr">volumeBindingMode:</span> <span class="hljs-string">WaitForFirstConsumer</span>
<span class="hljs-comment"># Volume is created only when a Pod actually uses the PVC</span>
</code></pre>
<h1 id="heading-common-scenarios">Common Scenarios</h1>
<h2 id="heading-the-same-name-scenario-reclaimpolicy-retain">The “Same Name” Scenario (ReclaimPolicy: Retain)</h2>
<p>Assume you have an application using a <strong>Persistent Volume (PV)</strong> named <code>pv-x</code>, and the reclaim policy is set to <strong>Retain</strong>. You deploy the app, it creates a <strong>PVC</strong>, and everything works fine. After some experimentation, you delete the application (which deletes the PVC).</p>
<p>Later, you:</p>
<ul>
<li>Create a <strong>new application</strong> with a <strong>different PVC name</strong>, but want to use the <strong>same data</strong></li>
</ul>
<ul>
<li>Or simply <strong>rename the PVC</strong> for the same application</li>
</ul>
<p>So… what happens?</p>
<p>When the PVC is deleted and the reclaim policy is <strong>Retain</strong>:</p>
<ul>
<li><p>The <strong>PV is NOT deleted.</strong> The <strong>underlying disk is preserved</strong></p>
</li>
<li><p>The PV moves into a <strong>Released</strong> state. The PV <strong>still remembers the old PVC</strong> it was bound to</p>
</li>
</ul>
<p>This is the important part 👇</p>
<p>A PV in the <strong>Released</strong> state <strong>cannot be automatically reused</strong> by a new PVC—even if:</p>
<ul>
<li><p>The storage size matches</p>
</li>
<li><p>The access modes match</p>
</li>
<li><p>The StorageClass matches</p>
</li>
</ul>
<p>Kubernetes intentionally blocks this to <strong>prevent accidental data leaks</strong> between workloads.</p>
<h3 id="heading-why-cant-a-new-pvc-reuse-the-same-pv-automatically">❓ Why Can’t a New PVC Reuse the Same PV Automatically?</h3>
<p>Because the PV still contains Old application data and Metadata pointing to the previous PVC. Kubernetes assumes this data belongs to someone else. I won’t reattach it unless a human explicitly says so. That’s a <strong>safety feature</strong>, not a limitation.</p>
<h3 id="heading-so-how-can-you-recover-the-data">So How Can You Recover the Data?</h3>
<p>There are <strong>two correct and safe ways</strong> to recover data from a retained PV.</p>
<p><strong>✅ Option 1: Manually Rebind the Existing PV (Most Common)</strong></p>
<p>If you want the <strong>same data</strong> to be used by a new or renamed PVC:</p>
<ul>
<li><p><strong>Manually edit the PV</strong> and remove the old <code>claimRef</code></p>
</li>
<li><p>Set the PV back to <code>Available</code></p>
<ul>
<li>Create a new PVC that matches: StorageSize, AccessMode, and Storage class</li>
</ul>
</li>
<li><p>Once this is done, Kubernetes will bind the new PVC to the old PV—and <strong>your data is recovered intact</strong>.</p>
</li>
</ul>
<p>This is the <strong>intended recovery flow</strong> for <code>Retain</code>.</p>
<p><strong>✅ Option 2: Create a New PV Pointing to the Same Disk</strong></p>
<p>This is useful when you don’t want to touch the old PV object and you want more control over the new binding.</p>
<ul>
<li><p>Identify the underlying disk (EBS volume, Azure Disk, etc.)</p>
</li>
<li><p>Create a <strong>new PV definition</strong></p>
</li>
<li><p>Point it to the <strong>same physical disk</strong></p>
</li>
<li><p>Create a new PVC that binds to this new PV</p>
</li>
</ul>
<p>The data remains untouched because the disk never changed—only the Kubernetes objects did.</p>
<h2 id="heading-do-you-know-aws-ebs-does-not-support-rwx">Do you know AWS EBS Does NOT Support RWX</h2>
<p>AWS <strong>Elastic Block Storage (EBS)</strong> does <strong>not</strong> support <code>ReadWriteMany (RWX)</code> access mode. why ?</p>
<p>Because an EBS volume behaves like a <strong>physical hard drive</strong>. Once you attach a hard drive to <strong>one machine</strong>, you cannot plug that same drive into <strong>multiple machines at the same time</strong> and expect it to work safely. This is exactly how EBS works:</p>
<ul>
<li><p>One EBS volume → one node</p>
</li>
<li><p>Read/write access → single node only</p>
</li>
</ul>
<p>That’s why EBS supports <code>ReadWriteOnce (RWO)</code>.This limitation is not a Kubernetes issue — it’s a <strong>block storage limitation</strong>.</p>
<h3 id="heading-so-what-if-you-need-rwx-across-multiple-nodes">So What If You Need RWX Across Multiple Nodes?</h3>
<p>If your application needs to run on multiple nodes and read and write the same data simultaneously, then <strong>block storage is the wrong tool</strong>. This is where <strong>file-based network storage</strong> comes in.</p>
<h3 id="heading-efs-the-rwx-friendly-storage">EFS: The RWX-Friendly Storage</h3>
<p><strong>Amazon Elastic File System (EFS)</strong> is designed to solve exactly this problem. EFS behaves like a <strong>shared network drive</strong>. Multiple nodes can mount it and all nodes see the same file and Read/Write operations happens concurrently</p>
<p>This wraps up our deep dive into <strong>PV, PVC, and StorageClasses</strong> — what they are, why they exist, and how Pods actually consume them.</p>
<p>From here, you can start mapping these concepts to <strong>real-world scenarios</strong> and production use cases, not just lab setups.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes - Volumes - Part-1]]></title><description><![CDATA[In this article we are going to explore Kubernetes Volumes together. We will discuss about what they are, why the exist and how they actually work behind the scene.
If you ever wondered “Where my data go when a pod restarts“ you are in a right place....]]></description><link>https://claybrainer.com/kubernetes-volumes-part-1</link><guid isPermaLink="true">https://claybrainer.com/kubernetes-volumes-part-1</guid><category><![CDATA[kubernetes hostPath]]></category><category><![CDATA[kubernetes emptyDir]]></category><category><![CDATA[k8-volumes]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Kubernetes Volumes]]></category><category><![CDATA[kubernetes-volume]]></category><category><![CDATA[hostpath]]></category><category><![CDATA[Emptydir]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 01 Feb 2026 14:12:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769508147001/a1960e3e-918d-4450-8d15-62f19585e919.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article we are going to explore <strong>Kubernetes Volumes</strong> together. We will discuss about what they are, why the exist and how they actually work behind the scene.</p>
<p>If you ever wondered “Where my data go when a pod restarts“ you are in a right place. Lets dive in 🚀</p>
<h2 id="heading-why-do-we-even-need-volumes">🤔 Why Do We Even Need Volumes?</h2>
<p>Let’s start with a simple question: <strong>why does Kubernetes need volumes at all?</strong></p>
<p>We know that a <strong>Pod</strong> is made up of one or more containers. And we also know an important (and slightly scary) fact about containers: <strong>Containers are ephemeral.</strong> This means when a container <strong>crashes, restarts, or gets recreated</strong>, its filesystem is wiped clean. Any files written inside the container? 💥 <em>Gone.</em></p>
<p>This is where <strong>Volumes</strong> come to the rescue. A <strong>Kubernetes Volume</strong> provides a way to <strong>decouple storage from the container’s lifecycle</strong>. So to answer the question :</p>
<h3 id="heading-why-we-use-volumes">🎯 Why We Use Volumes</h3>
<ul>
<li><p>Protect application data from container crashes</p>
</li>
<li><p>Share data between containers in a Pod</p>
</li>
<li><p>Make applications more reliable and production-ready</p>
</li>
</ul>
<p>Kubernetes offers <strong>multiple volume types</strong>, each designed for different use cases—temporary storage, shared storage, cloud disks, network storage, and more.</p>
<p>In the next sections, we’ll explore these volume options <strong>one by one</strong>, understand <strong>when to use what</strong>, and avoid common mistakes along the way.</p>
<hr />
<h1 id="heading-emptydir-the-simplest-kubernetes-volume">📦 emptyDir — The Simplest Kubernetes Volume</h1>
<p>The most basic volume type in Kubernetes is <code>emptyDir</code>. An <code>emptyDir</code> volume is created <strong>at the Pod level</strong>, not at the container level.</p>
<h2 id="heading-the-what">The What ?</h2>
<h3 id="heading-but-what-does-pod-level-actually-mean">👉 But what does <em>Pod level</em> actually mean?</h3>
<p>When a Pod is created: Kubernetes creates the <code>emptyDir</code> volume <strong>once.</strong> Every container inside that Pod can <strong>mount and access the same volume.</strong></p>
<p>So, the volume does <strong>not</strong> belong to a single container, it belongs to the pod itself. As long as the pod exists the volume exists</p>
<h3 id="heading-how-do-containers-share-an-emptydir">🤝 How Do Containers Share an emptyDir?</h3>
<p>Let’s make this real with a common and very practical scenario. Imagine a Pod with <strong>two containers</strong>:</p>
<ul>
<li><p><strong>App Container</strong>: Runs your main application and generate logs</p>
</li>
<li><p><strong>Sidecar Container</strong>: Collect those logs and ship them to a central logging system. You don’t want to add extra load or logging logic inside your main app, so you offload that responsibility to a <strong>sidecar container</strong>.</p>
</li>
</ul>
<p>Both containers mount the <strong>same</strong> <code>emptyDir</code> volume as a result the app writes the logs to the volume and sidecar reads logs from the same volume. They’re isolated containers but sharing data seamlessly.</p>
<p><strong>The important catch is data in the</strong> <code>emptyDir</code> <strong>volume will be lost when the pod restarts or crashed. But the data will be preserved even if any of the container in the pod crashes or restarted. Because</strong> <code>emptyDir</code> <strong>lives only as long as the Pod lives.</strong></p>
<p>So in simple terms <code>emptyDir</code>:</p>
<ul>
<li><p>Container lifecycle ❌ does NOT affect data</p>
</li>
<li><p>Pod lifecycle ✅ DOES affect data</p>
</li>
</ul>
<h2 id="heading-the-when">The When ?</h2>
<p><code>emptyDir</code> is ideal for:</p>
<ul>
<li><p>Temporary files</p>
</li>
<li><p>Cache data</p>
</li>
<li><p>Shared workspace between containers</p>
</li>
<li><p>Log sharing (like our sidecar example)</p>
</li>
</ul>
<p>🚫 It is <strong>not</strong> meant for long-term or critical data storage.</p>
<h2 id="heading-the-how"><strong>The How ?</strong></h2>
<p>Here’s a <strong>minimal working example</strong> of exactly the scenario we discussed.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">shared-workspace</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">producer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">busybox</span>
      <span class="hljs-comment"># Writes data to the volume</span>
      <span class="hljs-attr">volumeMounts:</span>          <span class="hljs-comment"># &lt;--- VolumeMount block (Producer)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/app/data</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">consumer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">busybox</span>
      <span class="hljs-comment"># Reads data from the volume</span>
      <span class="hljs-attr">volumeMounts:</span>          <span class="hljs-comment"># &lt;--- VolumeMount block (Consumer)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/app/input</span>
  <span class="hljs-attr">volumes:</span>                  <span class="hljs-comment"># &lt;--- Volume definition</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
      <span class="hljs-attr">emptyDir:</span> {}           <span class="hljs-comment"># &lt;--- The magic keyword</span>
</code></pre>
<h3 id="heading-lets-dissect-the-example-step-by-step">🔍 Let’s Dissect the Example Step by Step</h3>
<ul>
<li><p>Let’s begin at the bottom of the Pod spec—the <code>volumes</code> block. This is where we define</p>
<ul>
<li><p><strong>The volume name</strong> - <code>shared-data</code></p>
</li>
<li><p><strong>The volume type</strong> - <code>emptyDir</code></p>
</li>
</ul>
</li>
<li><p>This tells Kubernetes: Create an empty directory when the Pod starts and attach it to this Pod. At this point, the volume exists—but no container is using it yet.</p>
</li>
<li><p>Now look at the two containers inside the Pod: Each container has its own <code>volumeMounts</code> block. Both containers mount <strong>the same volume (</strong><code>shared-data</code>), but at <strong>different paths</strong>. This is <strong>intentional</strong>—and this is where things get interesting.</p>
</li>
</ul>
<p><strong>🤔 The Common Doubt (Very Natural One!)</strong></p>
<p>You might be thinking: If the producer writes logs to <code>/app/data</code>, how does the consumer read them from <code>/app/input</code>?</p>
<p>Think of the <strong>volume as a room.</strong> And think of <strong>mountPath as a door.</strong> A room can have <strong>multiple doors,</strong> no matter which door you enter, <strong>you end up in the same room</strong></p>
<p>With this analogy in our example <code>/app/data</code> is one door and <code>/app/input</code> is another door, both doors lead to the <strong>same</strong> <code>emptyDir</code> volume, so The <strong>producer</strong> writes logs into the room through the <code>/app/data</code> door and the <strong>consumer</strong> reads those same logs from the room through the <code>/app/input</code> door</p>
<p>With <code>emptyDir</code>, data survives container crashes and restarts. However, once the Pod is restarted or recreated, all the data in the volume is lost.</p>
<hr />
<p>But what if we want our data to survive even Pod restarts or crashes? That’s where <strong>hostPath</strong> comes in. Let’s take a look.</p>
<h1 id="heading-hostpath">📦 hostPath</h1>
<p>The main limitation of <code>emptyDir</code> is that the data is lost when the Pod restarts. This problem is addressed by <code>hostPath</code>.</p>
<h2 id="heading-the-what-1">The What ?</h2>
<p>With <code>hostPath</code>, the data is preserved <strong>no matter what happens to the Pod</strong>, as long as the Pod is scheduled on the <strong>same node</strong>.</p>
<h3 id="heading-how-does-this-work"><strong>🤔 How does this work?</strong></h3>
<p>The trick is simple: <code>hostPath</code> mounts a <strong>specific file or directory from the node’s filesystem</strong> directly into your Pod. So even if the Pod dies and a new Pod starts on the same node, the data is still there—because it never left the node in the first place.</p>
<h2 id="heading-the-when-1">The When?</h2>
<p>Use <code>hostPath</code> when:</p>
<ul>
<li><p>You want data to survive <strong>Pod restarts</strong></p>
</li>
<li><p>You are okay with the Pod running on the <strong>same node</strong></p>
</li>
<li><p>You are working in <strong>local development</strong>, <strong>single-node clusters</strong>, or <strong>testing environments</strong></p>
</li>
<li><p>You need access to <strong>node-level files</strong> (logs, sockets, configs)</p>
</li>
</ul>
<p>⚠️ Not recommended for multi-node production workloads due to portability and security concerns.</p>
<p><strong><em>⚠️ During node maintenance or a node crash, extra care is required. Any Pod using a</em></strong> <code>hostPath</code> <strong><em>volume will lose its data if it gets drained and rescheduled onto a different node. The data is preserved only as long as the Pod remains on the same node and the node is up and running.</em></strong></p>
<h2 id="heading-the-how-1"><strong>The How ?</strong></h2>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">shared-workspace</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">producer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">busybox</span>
      <span class="hljs-attr">volumeMounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/app/data</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">consumer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">busybox</span>
      <span class="hljs-attr">volumeMounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
          <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/app/input</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shared-data</span>
      <span class="hljs-attr">hostPath:</span> <span class="hljs-comment"># &lt;--- The magic keyword hostPath</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/tmp/shared-data</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">DirectoryOrCreate</span>
</code></pre>
<h3 id="heading-lets-dissect-the-example-step-by-step-1">🔍 Let’s Dissect the Example Step by Step</h3>
<ul>
<li><p>Instead of <code>emptyDir</code>, we now use <code>hostPath</code></p>
</li>
<li><p>The data is stored on the <strong>node at</strong> <code>/tmp/shared-data</code></p>
</li>
<li><p>The <code>type</code> field tells Kubernetes <strong>what it should expect at the given path on the node</strong> and <strong>what to do if it doesn’t exist</strong>. If it does <strong>not</strong> exist <strong>create the directory automatically</strong></p>
</li>
<li><p><strong>📦 Common</strong> <code>hostPath</code> <strong>Types</strong></p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Type</strong></td><td><strong>What it means</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>""</code> (empty string)</td><td>Default value. No checks are performed</td></tr>
<tr>
<td><code>DirectoryOrCreate</code></td><td>Uses the directory, or creates it if it does not exist</td></tr>
<tr>
<td><code>Directory</code></td><td>Directory <strong>must already exist</strong></td></tr>
<tr>
<td><code>FileOrCreate</code></td><td>Uses the file, or creates it if it does not exist</td></tr>
<tr>
<td><code>File</code></td><td>File <strong>must already exist</strong></td></tr>
<tr>
<td><code>Socket</code></td><td>Unix domain socket</td></tr>
<tr>
<td><code>CharDevice</code></td><td>Character device</td></tr>
<tr>
<td><code>BlockDevice</code></td><td>Block device</td></tr>
</tbody>
</table>
</div><h2 id="heading-common-doubts">🤔 Common Doubts</h2>
<p>You might have this question in mind: We know that <code>hostPath</code> is tied to a specific node. What happens if I restart the Pod or apply a rollout that recreates Pods? Is there any guarantee that the Pod will be scheduled on the same node where my data exists? And if it gets scheduled on a different node, will the data be lost?</p>
<p>Yes—<strong>if the Pod is scheduled on a different node, the data will be lost</strong>. In most cases, Kubernetes <strong>tries to reschedule the Pod onto the same node</strong> it was previously running on. This is why, during normal Pod restarts or rollouts, you often see the Pod coming back on the same node and your data appearing to be “safe.”</p>
<h3 id="heading-why-does-this-happen">❓ Why Does This Happen?</h3>
<p>When the scheduler evaluates where to place a Pod, it considers: Existing node assignments, Node availability, Resource constraints. If the node is Healthy, Not Drained, and has sufficient resources Kubernetes will typically place the pod back on the same node. ‘</p>
<p>However There is <strong>no strict guarantee</strong>. If the node is crashed, drained, under maintenance, or out of resources the pod will be rescheduled to different node.</p>
<hr />
<p>So far, we’ve seen how <strong>hostPath</strong> and <strong>emptyDir</strong> volumes work—great for temporary data, experiments, caching, and understanding how Kubernetes handles storage <em>inside</em> a Pod or a node. But what if you want your data to <strong>live beyond Pod restarts</strong>, survive <strong>node failures</strong>, and stay safe from all the usual Kubernetes chaos? 🤯</p>
<p>That’s exactly where <strong>Persistent Volumes (PV)</strong> come into the picture. They solve the problem of long-lived, reliable storage in Kubernetes. I’ve covered <strong>Persistent Volumes in detail in the next blog</strong>, breaking down how they work, why they matter, and when you should use them in real-world setups.</p>
<p>If you’ve made it this far — <strong>great job</strong> 👏 You now have a solid understanding of Kubernetes’ <em>ephemeral storage</em> story.</p>
<blockquote>
<p>👉 <a target="_blank" href="https://claybrainer.com/kubernetes-volumes-part-2?showSharer=true"><strong>Continue the journey here</strong></a><strong>:</strong> <em>Persistent Volumes in Kubernetes</em> — and let’s level up your storage game 🚀</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Linux CPU Performance Analysis with BPF Tools: A Practical Runbook]]></title><description><![CDATA[Recently, I got a chance to read BPF Performance Tools to brush up on my Linux skills. During my exploration, I came across some common CPU tools used for troubleshooting performance issues. I decided to create a runbook a practical guide that I (or ...]]></description><link>https://claybrainer.com/linux-cpu-performance-analysis-with-bpf-tools-a-practical-runbook</link><guid isPermaLink="true">https://claybrainer.com/linux-cpu-performance-analysis-with-bpf-tools-a-practical-runbook</guid><category><![CDATA[CPU Troubleshooting]]></category><category><![CDATA[BPF Tools]]></category><category><![CDATA[Linux Optimization]]></category><category><![CDATA[High CPU Usage]]></category><category><![CDATA[SRE Tools]]></category><category><![CDATA[Linux Debugging]]></category><category><![CDATA[linux-cpu]]></category><category><![CDATA[Linux Performance]]></category><category><![CDATA[system monitoring]]></category><category><![CDATA[Linux Performance Optimization]]></category><category><![CDATA[#Linux #Debugging #Troubleshooting #SystemAdministration #OpenSource #LearningByDoing #TechSkills #ProblemSolving #LinuxTips #PerformanceTuning #TerminalTips]]></category><category><![CDATA[user-mode-linux-kernel-debugging]]></category><category><![CDATA[Performance analysis]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Tue, 21 Oct 2025 09:18:22 GMT</pubDate><content:encoded><![CDATA[<hr />
<p>Recently, I got a chance to read <em>BPF Performance Tools</em> to brush up on my Linux skills. During my exploration, I came across some common CPU tools used for troubleshooting performance issues. I decided to create a runbook a practical guide that I (or anyone like me) can refer to whenever a CPU issue arises. This guide covers which tools to use, how to analyze their output, and how to draw actionable conclusions.</p>
<hr />
<h2 id="heading-basics-of-cpu">Basics of CPU</h2>
<p>A CPU consists of multiple <strong>cores</strong>, and each core can handle multiple tasks (instruction sets).</p>
<p>In cloud environments like AWS, when you say <strong>1 CPU</strong>, you are actually referring to <strong>1 vCPU (virtual CPU)</strong>.</p>
<p>✅ <strong>Key points:</strong></p>
<ul>
<li><p>1 vCPU = 1 hardware thread</p>
</li>
<li><p>On most Intel and AMD processors, <strong>1 physical core = 2 threads</strong> (thanks to hyper-threading)</p>
</li>
</ul>
<blockquote>
<p>Note: Here we are referring to <strong>cores</strong>, not the whole CPU. For example, a consumer laptop with an Intel i7 may have 7 cores. When we say 1 core, we mean 1 of those cores, not the entire CPU.</p>
</blockquote>
<p>So the mapping between physical cores and vCPUs looks like this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Physical Core</td><td>vCPUs</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>2</td></tr>
<tr>
<td>2</td><td>4</td></tr>
<tr>
<td>8</td><td>16</td></tr>
</tbody>
</table>
</div><p><strong>Example:</strong> AWS m5.xlarge → 2 vCPUs → 1 physical core with 2 threads</p>
<p>In Kubernetes, CPU limits are defined in <strong>millicores</strong>:</p>
<ul>
<li><p>1 CPU = 1000m</p>
</li>
<li><p>500m = 50% of a CPU</p>
</li>
</ul>
<hr />
<h2 id="heading-user-space-vs-kernel-space">User Space vs Kernel Space</h2>
<p>Processes in a CPU operate in two spaces:</p>
<ol>
<li><p><strong>User Space</strong> – where your applications and services run</p>
</li>
<li><p><strong>Kernel Space</strong> – system-level operations managed by the OS</p>
</li>
</ol>
<p><strong>Why this matters:</strong> When troubleshooting high CPU usage, it’s critical to know whether the load comes from user space or kernel space. Kernel-level processes need to be handled carefully because stopping them can affect the entire system.</p>
<p>Additionally, processes can be in two states:</p>
<ul>
<li><p><strong>Runnable (ONPROC)</strong> – ready and waiting for CPU execution</p>
</li>
<li><p><strong>Sleeping (idle)</strong> – waiting for resources or I/O</p>
</li>
</ul>
<hr />
<h2 id="heading-runbook-debugging-high-cpu-usage">Runbook: Debugging High CPU Usage</h2>
<p><strong>Scenario:</strong></p>
<p>You’re an SRE for an e-commerce platform. You get an alert:</p>
<p>🚨 <em>High CPU utilization on web-server-03 — 95% for the last 10 minutes</em></p>
<p><strong>Goal:</strong> Find the cause of high CPU usage.</p>
<hr />
<h3 id="heading-step-1-check-load-average-with-uptime">Step 1: Check Load Average with “<code>uptime</code>"</h3>
<pre><code class="lang-bash">uptime
</code></pre>
<p>This shows the average number of processes waiting to be executed over 1, 5, and 15 minutes.</p>
<p><strong>Example:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761035177611/071287a5-a46a-4178-845d-f95356637c38.png" alt class="image--center mx-auto" /></p>
<p><strong>How to interpret:</strong></p>
<ul>
<li><p>The three numbers represent the <strong>average number of processes waiting to run</strong> over different time intervals. <strong>These intervals are standard for uptime command</strong>:</p>
<ul>
<li><p><strong>6.45</strong> → 1-minute average</p>
</li>
<li><p><strong>5.89</strong> → 5-minute average</p>
</li>
<li><p><strong>4.12</strong> → 15-minute average</p>
</li>
</ul>
</li>
</ul>
<p>    <strong>What this tells you:</strong></p>
<ul>
<li><p>If you have <strong>2 CPUs</strong>, any value above 2 means the system has <strong>more processes waiting than CPUs available</strong>.</p>
</li>
<li><p>In this example, all three averages are above 2 → your CPUs are <strong>overloaded</strong>, and processes are queuing up for execution.</p>
</li>
<li><p>A <strong>high load average compared to CPU count</strong> indicates that your system is experiencing CPU pressure, and further investigation is needed to identify the culprits.</p>
</li>
</ul>
<blockquote>
<p>Tip: The <strong>1-minute average</strong> is most reactive to recent spikes, while the <strong>15-minute average</strong> shows longer-term trends..</p>
</blockquote>
<hr />
<h3 id="heading-step-2-identify-cpu-hungry-processes-with-top">Step 2: Identify CPU-Hungry Processes with “<code>top</code>"</h3>
<pre><code class="lang-bash">top
</code></pre>
<p><strong>Example:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761035308096/83bc786e-6dff-4807-8082-a6bb5dffe37f.png" alt class="image--center mx-auto" /></p>
<p><strong>What to look for:</strong></p>
<p><strong>Highlighted Field in Orange Color Defines:</strong></p>
<ul>
<li><p><strong>%us</strong> → CPU usage in user space</p>
</li>
<li><p><strong>%sy</strong> → CPU usage in system/kernel space</p>
</li>
</ul>
<p>This helps determine whether the CPU load is from user applications or kernel processes.</p>
<p>Sort processes by CPU usage for easier analysis:</p>
<pre><code class="lang-bash">top -o %CPU
</code></pre>
<p><strong>Dig deeper:</strong></p>
<pre><code class="lang-bash">pidstat -p &lt;PID&gt;
</code></pre>
<ul>
<li>You can use pidstat to get the detailed information about the specific Process id which you can get it from top command.</li>
</ul>
<pre><code class="lang-bash">sudo strace -p &lt;PID&gt;
</code></pre>
<ul>
<li>This command will give you detailed report on the PID. If you really need to go deeper use strace command, mostly pidstat is sufficient enough for troubleshooting.</li>
</ul>
<p><strong>Sample Output of Strace:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761035421661/3e019301-d350-4f76-8cd8-68fc927ae54c.png" alt class="image--center mx-auto" /></p>
<p><strong>How to read:</strong></p>
<ul>
<li><p>Each line = 1 syscall.</p>
</li>
<li><p>The last number (e.g. <code>= 17</code>) is the <strong>return value</strong>.</p>
</li>
<li><p>If you see one syscall repeated rapidly (like <code>read()</code> or <code>epoll_wait()</code>), that’s the loop burning CPU.</p>
</li>
</ul>
<p>If you suspect it’s looping too fast:</p>
<pre><code class="lang-bash">sudo strace -c -p &lt;PID&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761035496112/640c3951-dd52-44ed-a7ce-6effc16e4560.png" alt class="image--center mx-auto" /></p>
<p><strong>Possible remediation:</strong></p>
<ol>
<li><p>Check and restart the service</p>
</li>
<li><p>Kill non-critical processes and restart later</p>
</li>
</ol>
<hr />
<h3 id="heading-step-3-check-cpu-distribution-across-cores-using-mpstat">Step 3: Check CPU Distribution Across Cores using “<code>mpstat</code>“</h3>
<p>Sometimes only a few CPUs are maxed out while others are idle. This can happen if workloads are:</p>
<ul>
<li><p>Single-threaded</p>
</li>
<li><p>CPU-pinned (affinity set)</p>
</li>
<li><p>Blocked by locks</p>
</li>
</ul>
<p>Use <code>mpstat</code> to see <strong>per-core utilization</strong>:</p>
<pre><code class="lang-bash">mpstat -P ALL 2 3
</code></pre>
<ul>
<li><p><strong>2</strong> → interval of 2 seconds</p>
</li>
<li><p><strong>3</strong> → run 3 times</p>
</li>
</ul>
<p>This shows CPU usage per core and helps identify imbalances.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761036126601/762cc6ba-f6ac-4bb0-993b-eabafe3aa5a9.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-step-4-use-bpf-tools-for-deep-analysis">Step 4: Use BPF Tools for Deep Analysis</h3>
<p>Sometimes <code>top</code> is not enough. Use BPF (Berkeley Packet Filter) tools when:</p>
<ul>
<li><p>High CPU is confirmed but the exact cause is unclear</p>
</li>
<li><p>Kernel or syscall usage is high</p>
</li>
<li><p>You suspect locks, spin loops, or scheduler delays</p>
</li>
</ul>
<h4 id="heading-1-profile">1. <code>profile</code></h4>
<p>Shows which functions consume CPU:</p>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/profile 5
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761036183863/85bccf9d-207f-4901-9461-533d5cfc5065.png" alt class="image--center mx-auto" /></p>
<p><strong>Interpretation:</strong></p>
<ul>
<li><p>Each block shows a call stack and the number of samples (e.g. <code>45</code> means CPU was in that stack 45 times).</p>
</li>
<li><p>The higher the count, the more CPU time that function consumes.</p>
</li>
<li><p>Helps pinpoint the exact code path burning CPU.</p>
</li>
</ul>
<p>User-space only:</p>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/profile -U
</code></pre>
<h4 id="heading-2-offcputime">2. <code>offcputime</code></h4>
<p>Shows where threads are waiting (blocked, sleeping, or I/O wait):</p>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/offcputime 5
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761036250143/5becfca6-532a-49f6-9517-4d4bf18fd7eb.png" alt class="image--center mx-auto" /></p>
<p><strong>Interpretation:</strong></p>
<ul>
<li><p>This means the thread is spending time <em>waiting</em> in a futex (a synchronisation lock).</p>
</li>
<li><p>So CPU isn’t overloaded by raw computation — it’s waiting on something (like a lock or I/O).</p>
</li>
<li><p>Combine this with <code>profile</code>:</p>
<ul>
<li><p><code>profile</code> → what’s <em>using</em> CPU</p>
</li>
<li><p><code>offcputime</code> → what’s <em>waiting</em> for CPU</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-3-runqlen">3. <code>runqlen</code></h4>
<p>Shows the run queue length per CPU:</p>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/runqlen
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761036304140/affd2e55-63c8-4f13-b96c-703e58996401.png" alt class="image--center mx-auto" /></p>
<p><strong>Interpretation:</strong></p>
<ul>
<li><p>Average 2.5 → 2.5 tasks waiting to run on CPU0 most of the time.</p>
</li>
<li><p>High numbers mean <strong>CPU contention</strong> — more runnable tasks than CPUs.</p>
</li>
<li><p>If this matches a high load average, you’ve confirmed CPU saturation.</p>
</li>
</ul>
<hr />
<h3 id="heading-summary-how-these-tools-fit-together">🧠 Summary: How These Tools Fit Together</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Tool</strong></td><td><strong>Purpose</strong></td><td><strong>When to Use</strong></td></tr>
</thead>
<tbody>
<tr>
<td>uptime</td><td>Shows load average (processes waiting)</td><td>Initial check to see CPU load vs available CPUs</td></tr>
<tr>
<td>top</td><td>Displays CPU usage per process and space</td><td>Identify high CPU processes and user/kernel usage</td></tr>
<tr>
<td>mpstat</td><td>Per-core CPU utilization</td><td>Detect load imbalance across cores</td></tr>
<tr>
<td>strace</td><td>Syscalls by a process</td><td>Process-level view</td></tr>
<tr>
<td>profile</td><td>Functions consuming CPU</td><td>High CPU in user/kernel</td></tr>
<tr>
<td>offcputime</td><td>Threads waiting (blocked, sleeping, I/O)</td><td>Performance stalls, I/O wait</td></tr>
<tr>
<td>runqlen</td><td>Threads waiting per CPU</td><td>Confirm CPU contention</td></tr>
</tbody>
</table>
</div><p><strong>Workflow:</strong></p>
<ul>
<li><p><code>uptime</code> → check load average vs available CPUs</p>
</li>
<li><p><code>top</code> → confirm high CPU usage</p>
</li>
<li><p><code>mpstat</code> → verify per-core load distribution</p>
</li>
<li><p><code>profile</code> → find which functions burn CPU</p>
</li>
<li><p><code>offcputime</code> → find functions waiting off CPU</p>
</li>
<li><p><code>runqlen</code> → verify CPU contention</p>
</li>
<li><p><code>strace</code> → check syscalls causing delays</p>
</li>
</ul>
<hr />
<h2 id="heading-mini-lab-investigating-high-cpu-usage">🧩 Mini Lab: Investigating High CPU Usage</h2>
<p>Follow these steps to practice analyzing CPU issues on a test server:</p>
<ol>
<li><strong>Simulate CPU load:</strong></li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># Stress CPU for 60 seconds</span>
sudo apt install stress -y
stress --cpu 2 --timeout 60
</code></pre>
<ol start="2">
<li><strong>Check load averages:</strong></li>
</ol>
<pre><code class="lang-bash">uptime
</code></pre>
<ol start="3">
<li><strong>Identify CPU-hungry processes:</strong></li>
</ol>
<pre><code class="lang-bash">top -o %CPU
</code></pre>
<ol start="4">
<li><strong>Check per-core utilization:</strong></li>
</ol>
<pre><code class="lang-bash">mpstat -P ALL 2 3
</code></pre>
<ol start="5">
<li><strong>Profile functions consuming CPU:</strong></li>
</ol>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/profile 5
</code></pre>
<ol start="6">
<li><strong>Check where threads are blocked:</strong></li>
</ol>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/offcputime 5
</code></pre>
<ol start="7">
<li><strong>Verify run queue length:</strong></li>
</ol>
<pre><code class="lang-bash">sudo /usr/share/bcc/tools/runqlen
</code></pre>
<ol start="8">
<li><strong>Investigate syscalls for a process:</strong></li>
</ol>
<pre><code class="lang-bash">pidstat -p &lt;PID&gt;
strace -p &lt;PID&gt;
sudo strace -c -p &lt;PID&gt;
</code></pre>
<p>By completing this mini-lab, you’ll have hands-on experience with CPU troubleshooting using both traditional and BPF tools.</p>
<hr />
<p><em>Happy Troubleshooting!</em></p>
]]></content:encoded></item><item><title><![CDATA[DinD (Docker in Docker)]]></title><description><![CDATA[🚧 Problem Statement
Imagine you're running a Jenkins server inside a Docker container. Everything works fine—until your CI pipeline tries to execute docker build or docker push. Suddenly, your workflow fails. But why
🔍 Root Cause
Docker commands li...]]></description><link>https://claybrainer.com/dind-docker-in-docker</link><guid isPermaLink="true">https://claybrainer.com/dind-docker-in-docker</guid><category><![CDATA[jenkins-container]]></category><category><![CDATA[docker-sock]]></category><category><![CDATA[dind]]></category><category><![CDATA[Docker]]></category><category><![CDATA[docker cli]]></category><category><![CDATA[Docker jenkins]]></category><category><![CDATA[jenkins pipeline]]></category><category><![CDATA[#devops #jenkins #docker #integration #ci/cd]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sat, 19 Jul 2025 14:33:39 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-problem-statement">🚧 Problem Statement</h3>
<p>Imagine you're running a Jenkins server inside a Docker container. Everything works fine—until your CI pipeline tries to execute <code>docker build</code> or <code>docker push</code>. Suddenly, your workflow fails. But why</p>
<h3 id="heading-root-cause">🔍 Root Cause</h3>
<p>Docker commands like <code>build</code> or <code>push</code> are executed by the <strong>Docker daemon</strong> (<code>dockerd</code>), which is the core engine of Docker running on the host system. Communication with this daemon happens through a Unix socket called <code>docker.sock</code>.</p>
<p>When a Docker client (like the Jenkins container) sends a command, it’s routed via <code>docker.sock</code> to the Docker daemon, which performs the actual operation and returns the result.</p>
<p>Here’s the catch: <strong>you cannot run the Docker daemon itself inside a Docker container</strong> in the traditional way. Why? Let’s break it down.</p>
<h3 id="heading-why-cant-you-run-docker-daemon-inside-a-docker-container-in-straight-forward-approach">❓ Why Can’t You Run Docker Daemon Inside a Docker Container in Straight Forward approach ?</h3>
<p>The Docker daemon needs deep access to the host system—including the kernel and several privileged operations that aren’t typically available inside a container. Since containers are isolated environments sharing the host's kernel, running a full Docker server inside another container breaks this isolation model and leads to permission and capability issues.</p>
<p>In short:</p>
<ul>
<li><p>Docker requires host-level access.</p>
</li>
<li><p>Containers are not meant to emulate full virtual machines.</p>
</li>
<li><p>Running <code>dockerd</code> inside a container is not straightforward and can introduce security and stability risks.</p>
</li>
</ul>
<h2 id="heading-solution">✅ Solution</h2>
<p>From our earlier discussion, we learned that Docker commands need to be routed through <code>docker.sock</code>, which communicates with the Docker daemon. Since <code>docker.sock</code> is part of the Docker server component running on the host, we must tell our application container—like Jenkins—how to access it.</p>
<p>There are <strong>two common approaches</strong> to enable Docker commands from inside a container:</p>
<h4 id="heading-1-mounting-the-hosts-docker-socket">🔗 <strong>1. Mounting the Host’s Docker Socket</strong></h4>
<p>In this approach, we directly mount the host’s <code>docker.sock</code> into the Jenkins container:</p>
<pre><code class="lang-bash">docker run -d \
  --name jenkins-docker \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts
</code></pre>
<p>This allows Jenkins to communicate with the host's Docker daemon just as if it were running natively on the host.</p>
<p>✅ <strong>Pros:</strong></p>
<ul>
<li><p>Simple to set up.</p>
</li>
<li><p>No need to run another Docker daemon.</p>
</li>
</ul>
<p>❌ <strong>Cons:</strong></p>
<ul>
<li><p><strong>Security risk</strong>: You're giving full control of the host’s Docker engine to the container.</p>
</li>
<li><p>If compromised, the Jenkins container can perform any operation on your host, including modifying or deleting containers and images.</p>
</li>
</ul>
<h4 id="heading-2-using-a-docker-in-docker-dind-container">🐳 <strong>2. Using a Docker-in-Docker (DinD) Container</strong></h4>
<p>A more secure and isolated approach is to use a <strong>Docker-in-Docker (DinD)</strong> container. This is a special Docker image that runs its own Docker daemon inside a container.</p>
<p>Here's how it works:</p>
<ol>
<li>You run a DinD container (based on the official <code>docker:dind</code> image).</li>
</ol>
<pre><code class="lang-bash">docker run -d \
  --name dind \
  --privileged \
  --network jenkins-net \
  -e DOCKER_TLS_CERTDIR= <span class="hljs-string">""</span> \
  -p 2375:2375 \
  docker:dind
</code></pre>
<ol start="2">
<li><p>You configure your Jenkins container to point to this DinD container’s Docker daemon instead of the host's.</p>
</li>
<li><p>You set the <code>DOCKER_HOST</code> environment variable in your Jenkins container to the DinD container’s Docker socket, e.g.:</p>
<pre><code class="lang-bash"> docker run -d \
   --name jenkins-docker \
   --network jenkins-net \
   -p 8080:8080 \
   -p 50000:50000 \
   -v jenkins_home:/var/jenkins_home \
   -e DOCKER_HOST=tcp://dind:2375 \
   jenkins/jenkins:lts
</code></pre>
</li>
</ol>
<blockquote>
<p>✅ <code>-e DOCKER_HOST=tcp://dind:2375</code>: Tells Jenkins to send Docker commands to the DinD container (hostname <code>dind</code> on port <code>2375</code>).</p>
</blockquote>
<p>✅ <strong>Pros:</strong></p>
<ul>
<li><p>Isolates Docker operations from the host.</p>
</li>
<li><p>Limits Docker access to the DinD environment only.</p>
</li>
<li><p>Safer for shared or multi-tenant CI setups.</p>
</li>
</ul>
<p>❌ <strong>Cons:</strong></p>
<ul>
<li><p>Slightly more complex to set up.</p>
</li>
<li><p>Performance overhead compared to using the host daemon.</p>
</li>
<li><p>Security concerns still exist (DinD requires privileged mode), but it's safer than exposing the host’s socket.</p>
</li>
<li><p>This setup disables TLS and runs the DinD container in privileged mode. That’s fine for <strong>testing, learning, or internal CI</strong> setups, but not recommended for production.</p>
</li>
</ul>
<p>📌 <strong>Important Note</strong>: Make sure both the Jenkins container and the DinD container are running on the <strong>same Docker network</strong> so they can communicate.</p>
]]></content:encoded></item><item><title><![CDATA[Learning By Doing Golang - ToDo App Part 3.1 - Application Layer (Core Logic)]]></title><description><![CDATA[Now that we've set up our database and created the schema for our ToDo app, it's time to focus on the core logic — what we call the Application Layer of the ToDo app.
If you're just joining the journey, you can follow the entire series from here:
🧠 ...]]></description><link>https://claybrainer.com/learning-by-doing-golang-todo-app-part-31-application-layer-core-logic</link><guid isPermaLink="true">https://claybrainer.com/learning-by-doing-golang-todo-app-part-31-application-layer-core-logic</guid><category><![CDATA[golang-application]]></category><category><![CDATA[learning-by-doing-golang]]></category><category><![CDATA[golang-todo]]></category><category><![CDATA[Golang Learning]]></category><category><![CDATA[golang]]></category><category><![CDATA[go net http]]></category><category><![CDATA[net/http]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sat, 21 Jun 2025 07:59:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750492675879/1539bacf-133d-4833-ae5e-306514bc5f54.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Now that we've set up our database and created the schema for our ToDo app, it's time to focus on the core logic — what we call the <strong>Application Layer</strong> of the ToDo app.</p>
<p>If you're just joining the journey, you can follow the entire series from <a target="_blank" href="https://claybrainer.com/series/learning-by-doing-golang">here</a>:</p>
<h2 id="heading-setting-the-context-the-core-logic-of-our-app">🧠 Setting the Context: The Core Logic of Our App</h2>
<p>The <strong>core functionality</strong> of our ToDo application includes:</p>
<ul>
<li><p>✅ Creating a ToDo item</p>
</li>
<li><p>✏️ Updating a ToDo item</p>
</li>
<li><p>❌ Deleting a ToDo item</p>
</li>
<li><p>📄 Reading (fetching) ToDo items</p>
</li>
</ul>
<p>In this section, we’ll implement the application logic that powers these features.</p>
<p>We'll be writing code that defines what the app should do <strong>when a user makes a specific request</strong> — and what kind of response the app should return.</p>
<h2 id="heading-how-are-we-going-to-do-this">🛠️ How Are We Going to Do This?</h2>
<p>Just like in the previous blogs of this series, we’ll follow a <strong>two-step approach</strong>:</p>
<ol>
<li><p><strong>Start with the basic (native) approach</strong> to understand how things work at the core level.</p>
</li>
<li><p><strong>Evaluate and adopt a better alternative library</strong> that simplifies and enhances the process.</p>
</li>
<li><p>Use the selected method to <strong>implement the entire logic</strong> and <strong>test</strong> it end-to-end.</p>
</li>
</ol>
<p>Let’s get started and build the <strong>Application Layer</strong> of our ToDo app! 🚀</p>
<h3 id="heading-understanding-http-in-our-web-application">🌐 Understanding HTTP in Our Web Application</h3>
<p>Since we’re building a <strong>web application</strong>, all user interactions will happen through <strong>HTTP requests</strong>. We should handle this request to serve the user.</p>
<p>💡 <em>Wait… what do you mean by handling HTTP requests?</em></p>
<p>Great question! In a web app, users interact with the backend using HTTP — the protocol behind the web. For example, if a user wants to <strong>create a ToDo item</strong>, they would make a request like:</p>
<pre><code class="lang-bash">curl -X POST http://mytodoapp/create \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "task_name": "Write blog post",
    "description": "Complete the blog post for the Golang ToDo app",
    "status": "NotStarted",
    "end_date": "2024-06-30T23:59:00Z"
  }'</span>
</code></pre>
<p>You might be wondering: <em>“What exactly is that?”</em> Don’t worry — we’ll cover it in more detail soon. For now, just understand that users will interact with our app using <strong>HTTP URLs</strong> and <strong>endpoints</strong> (like <code>/create</code>, <code>/update</code>, etc.) to trigger certain actions. And when we say <em>"handling HTTP requests"</em>, we mean:</p>
<blockquote>
<p>The application should be able to <strong>receive</strong> the request, <strong>extract</strong> any information from it (like request body or URL parameters), and <strong>perform the appropriate action</strong> in response.</p>
</blockquote>
<p><strong>How Do We Handle HTTP in Golang?</strong> To handle HTTP in Go, there are several libraries available. One of the most basic and native options is: <strong>net/http</strong></p>
<h3 id="heading-lets-start-with-creating-a-todo-item">🛠 Let’s Start with Creating a ToDo Item</h3>
<p>To begin, we’ll focus on one core operation: <strong>creating a new ToDo item</strong>.</p>
<p>We’ll implement an HTTP handler using the <code>net/http</code> package that listens for a <code>POST</code> request, extracts data from the request body, and passes it to our application logic (which we already connected to the database layer).</p>
<p>In the next section, we’ll walk through how to:</p>
<ol>
<li><p>Define an HTTP handler function</p>
</li>
<li><p>Read and parse the JSON body from the client</p>
</li>
<li><p>Validate and use the data to create a new ToDo</p>
</li>
<li><p>Return a meaningful response back to the user</p>
</li>
</ol>
<p>Let’s dive in and build the <code>POST /todo/create</code> endpoint using <code>net/http</code>.</p>
<h4 id="heading-recollecting-what-we-will-be-doing">🚦 Recollecting what we will be doing</h4>
<p>From the previous step, we now understand that:</p>
<ul>
<li><p>✅ We need to <strong>import an HTTP handler</strong> to allow our Go application to serve web requests</p>
</li>
<li><p>✅ To read and parse the incoming request body (usually in JSON), we need a package for decoding JSON. A quick Google search shows that Go provides a standard library for this:<code>"encoding/json"</code></p>
</li>
<li><p>✅ We need to write the logic to:</p>
<ol>
<li><p>Receive incoming HTTP requests</p>
</li>
<li><p>Read the data (like a new ToDo item) from the request body</p>
</li>
<li><p>Pass it to our business logic (e.g., <code>CreateToDo</code>)</p>
</li>
<li><p>Return a proper response to the user</p>
</li>
</ol>
</li>
</ul>
<h3 id="heading-lets-code-from-where-we-left">▶️ Let’s Code from Where We Left</h3>
<p>If you're following along, continue from the code snippet in the last blog. We'll now build the <code>ToDoHanlderCreate</code> using the <code>net/http</code> and <code>encoding/json</code> packages.</p>
<p>Let’s plug that into our <code>main.go</code> file <a target="_blank" href="https://claybrainer.com/learning-by-doing-golang-todo-app-part-2-2-creating-database-layer#:~:text=Your%20code%20so%20far%20should%20include%3A">from our last blog and get started</a> 🚀</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"gorm.io/driver/mysql"</span>
    <span class="hljs-string">"gorm.io/gorm"</span>
)

<span class="hljs-keyword">var</span> (
    <span class="hljs-comment">// Declare a global variable to hold the DB connection</span>
    db *gorm.DB <span class="hljs-comment">// Type: pointer to gorm.DB</span>
)

<span class="hljs-keyword">type</span> ToDo <span class="hljs-keyword">struct</span>{
    gorm.Model
    TaskName    <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"not null" json:"title"`</span>
    Description <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"type text" json:"description"`</span>
    Status      <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"default:NotStarted" json:"status" validate:"required,oneof=NotStarted InProgress Pending Completed"`</span>
    EndDate     *time.Time <span class="hljs-string">`json:"end_date"`</span>
}

<span class="hljs-comment">// ConnectDB establishes the connection to the MySQL database</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ConnectDB</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Data Source Name (DSN): contains database connection info</span>
    dsn := <span class="hljs-string">"root:password@tcp(127.0.0.1:3306)/todo?charset=utf8&amp;parseTime=True&amp;loc=Local"</span>

    <span class="hljs-comment">// Connect to the DB using GORM and MySQL driver</span>
    db_conn, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config{})
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-comment">// If connection fails, panic and log the error</span>
        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"Failed to connect to the database: "</span> + err.Error())
    }

    <span class="hljs-comment">/*Why I'm passing value here why can't I pass it like db,err. 
    If I do that I have to either add err variable globally or remove db global var as I'm using := so I used a var to exchnage value
    */</span>
    db = db_conn 
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InitDB</span><span class="hljs-params">()</span></span>{
    ConnectDB() <span class="hljs-comment">// This will create new connection and the value is stored in DB variable</span>

    <span class="hljs-comment">// Here we are saying that Use the Schema &amp;ToDo and create table using ou DB connection 'db'</span>
    <span class="hljs-comment">//'{}' defines that create empty table with that ToDo Struct Schema</span>
    db.AutoMigrate(&amp;ToDo{})
}

<span class="hljs-comment">// CreateToDo inserts a new ToDo item into the database.</span>
<span class="hljs-comment">// It takes a pointer to a ToDo struct as input and returns the same pointer after insertion.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">CreateToDo</span><span class="hljs-params">(todo *ToDo)</span> *<span class="hljs-title">ToDo</span></span> {
    <span class="hljs-comment">// Use GORM's Create method to insert the new record into the database.</span>
    <span class="hljs-comment">// The &amp;todo tells GORM to insert the data from the memory location of the passed ToDo.</span>
    db.Create(&amp;todo)
    <span class="hljs-comment">// Return the same pointer, now updated with DB-generated fields (like ID, CreatedAt, etc.)</span>
    <span class="hljs-keyword">return</span> todo
}

<span class="hljs-comment">// .... Truncating other code you can refer from the link provided above</span>
...
...
...
</code></pre>
<p>Let’s import and modify the code</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"gorm.io/driver/mysql"</span>
    <span class="hljs-string">"gorm.io/gorm"</span>
    <span class="hljs-string">"time"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"encoding/json"</span>
)

<span class="hljs-keyword">var</span> (
    <span class="hljs-comment">// Declare a global variable to hold the DB connection</span>
    db *gorm.DB <span class="hljs-comment">// Type: pointer to gorm.DB</span>
)

<span class="hljs-keyword">type</span> ToDo <span class="hljs-keyword">struct</span>{
    gorm.Model
    TaskName    <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"not null" json:"title"`</span>
    Description <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"type text" json:"description"`</span>
    Status      <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"default:NotStarted" json:"status" validate:"required,oneof=NotStarted InProgress Pending Completed"`</span>
    EndDate     *time.Time <span class="hljs-string">`json:"end_date"`</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ConnectDB</span><span class="hljs-params">()</span></span> {...}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InitDB</span><span class="hljs-params">()</span></span>{...}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ToDoHanlderCreate</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span>{
    <span class="hljs-comment">// step1: Ensure it's a Post request</span>
    <span class="hljs-keyword">if</span> r.Method != http.MethodPost {
     http.Error(w, <span class="hljs-string">"Only Post method is allowed"</span>, http.StatusMethodNotAllowed)
    <span class="hljs-keyword">return</span>
    }
    <span class="hljs-comment">// Step 2: Parse the incoming JSON body into a ToDo struct</span>
    <span class="hljs-keyword">var</span> todo ToDo
    err := json.NewDecoder(r.Body).Decode(&amp;todo)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Invalid Json Input"</span>, http.StatusBadRequest)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-comment">// Step 3: Save the new ToDo item to the database</span>
    db.Create(&amp;todo) <span class="hljs-comment">// Using Gorm Native create object option</span>
    <span class="hljs-comment">// Step 4: Set the content type and return the created object as JSON</span>
    w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>) <span class="hljs-comment">// Set response type as JSON</span>
    w.WriteHeader(http.StatusCreated) <span class="hljs-comment">// Set HTTP status code to 201 (Created)</span>
    json.NewEncoder(w).Encode(todo) <span class="hljs-comment">// Write the todo object as JSON in response</span>


}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    InitDB() <span class="hljs-comment">// Initialize DB</span>

    <span class="hljs-comment">// Register the route and handler</span>
    http.HandleFunc(<span class="hljs-string">"/todo/create"</span>, ToDoHanlderCreate)

    fmt.Println(<span class="hljs-string">"🚀 Server running at http://localhost:8080"</span>)
    err := http.ListenAndServe(<span class="hljs-string">":8080"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"Error starting server:"</span>, err)
}
</code></pre>
<p>Lets breakdown the <code>ToDoHanlderCreate</code> function and how we change main.</p>
<h4 id="heading-step-1-todohandlerfunction">Step : 1 - ToDoHandlerFunction</h4>
<ul>
<li><p><code>func ToDoHanlderCreate(w http.ResponseWriter, r *http.Request){…}</code></p>
<p>  📥 <code>r *http.Request</code></p>
<ul>
<li><p>This parameter is used to <strong>receive and read the incoming HTTP request</strong> from the user.</p>
</li>
<li><p>It contains:</p>
<ul>
<li><p>Request method (e.g., GET, POST)</p>
</li>
<li><p>Headers</p>
</li>
<li><p>Query parameters</p>
</li>
<li><p>Request body (like form data or JSON payload)</p>
</li>
</ul>
</li>
<li><p>We're using a pointer (<code>*http.Request</code>) so we’re accessing the actual request data without copying it.</p>
</li>
<li><p>We store it in a variable named <code>r</code> so we can access its fields inside the function.</p>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>Example: To read the JSON request body, we'll use <code>r.Body</code>.</p>
</blockquote>
<p>    📤 <code>w http.ResponseWriter</code></p>
<ul>
<li><p>This parameter is used to <strong>send the response back to the user</strong>.</p>
</li>
<li><p>It lets us:</p>
<ul>
<li><p>Write text or JSON data as the response</p>
</li>
<li><p>Set response status codes (like 200 OK or 400 Bad Request)</p>
</li>
<li><p>Set response headers (like <code>Content-Type</code>)</p>
</li>
</ul>
</li>
<li><p>Anything you write to <code>w</code> is what the client receives.</p>
</li>
</ul>
<blockquote>
<p>Example: To send back a success message, we can use <code>w.Write([]byte("ToDo created"))</code>.</p>
</blockquote>
<h4 id="heading-step-2-json-decoding">Step : 2 - Json Decoding</h4>
<pre><code class="lang-go">    <span class="hljs-comment">// Step 2: Parse the incoming JSON body into a ToDo struct</span>
    <span class="hljs-keyword">var</span> todo ToDo
    err := json.NewDecoder(r.Body).Decode(&amp;todo)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Invalid Json Input"</span>, http.StatusBadRequest)
        <span class="hljs-keyword">return</span>
    }
</code></pre>
<ul>
<li><p>Here, we create a variable called <code>todo</code>, which is of type <code>ToDo</code> <code>struct</code>. We need this variable because once we extract the content from the HTTP request body, we store it in this variable so we can use it later—for example, to insert it into the database.</p>
</li>
<li><p><code>err := json.NewDecoder(r.Body).Decode(&amp;todo)</code> - This line uses Go’s <code>encoding/json</code> package to decode the JSON data from the request body and assign it to the <code>todo</code> variable.</p>
</li>
<li><p>After decoding, we handle any potential error—this helps us ensure that the request body is in the correct format. If it's not, we can return a proper error message to the client.</p>
</li>
</ul>
<h4 id="heading-step-3-create-todo">Step : 3 - Create ToDo</h4>
<p><code>db.Create(&amp;todo)</code> - We use GORM’s built-in <code>Create</code> method to add a new entry to our database. The data we’re inserting comes from the <code>todo</code> variable, which already holds the content we extracted and decoded from the incoming HTTP request. By passing <code>&amp;todo</code>, we're telling GORM to insert the data from that memory location into the corresponding table.</p>
<h4 id="heading-step-4-response-to-user">Step : 4 - Response to user</h4>
<pre><code class="lang-go">w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>) <span class="hljs-comment">// Set response type as JSON</span>
w.WriteHeader(http.StatusCreated)                 <span class="hljs-comment">// Set HTTP status code to 201 (Created)</span>
json.NewEncoder(w).Encode(todo)                   <span class="hljs-comment">// Write the todo object as JSON in response</span>
</code></pre>
<ul>
<li><p><code>w.Header().Set("Content-Type", "application/json")</code> - Tells the client that you're sending back JSON data.</p>
</li>
<li><p><code>w.WriteHeader(http.StatusCreated)</code>- Sends an HTTP status code <strong>201 Created</strong>, which is the correct status code for successful resource creation.</p>
</li>
<li><p><code>json.NewEncoder(w).Encode(todo)</code> - Converts the <code>todo</code> struct to JSON and writes it to the response body.</p>
</li>
</ul>
<h4 id="heading-main-function"><strong>main() Function:</strong></h4>
<pre><code class="lang-go">InitDB() <span class="hljs-comment">// Initialize DB</span>

    <span class="hljs-comment">// Register the route and handler</span>
    http.HandleFunc(<span class="hljs-string">"/todo/create"</span>, ToDoHanlderCreate)

    fmt.Println(<span class="hljs-string">"🚀 Server running at http://localhost:8080"</span>)
    err := http.ListenAndServe(<span class="hljs-string">":8080"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"Error starting server:"</span>, err)
</code></pre>
<ul>
<li><p><code>InitDB()</code> This function initializes the database connection using GORM and MySQL. It also runs the AutoMigrate() function, which ensures that our ToDo model is translated into a table in the database.</p>
</li>
<li><p><code>http.HandleFunc("/todo/create", ToDoHandlerCreate)</code> Registering the Route. This means that any <code>POST</code> request to <code>/todo/create</code> will be handled by the <code>ToDoHandlerCreate</code> function. This is the entry point for users to <strong>create new ToDo items</strong>.</p>
</li>
<li><p><code>http.ListenAndServe(":8080", nil)</code> Starting the HTTP Server. This tells Go to start an HTTP server on port <code>8080</code>. As long as the server is running, it will listen for incoming HTTP requests on that port.</p>
</li>
</ul>
<h3 id="heading-summary">✅ Summary</h3>
<ul>
<li><p><strong>Database</strong>: Initialized and migrated with <code>InitDB()</code></p>
</li>
<li><p><strong>Route</strong>: <code>/todo/create</code> handles new ToDo creation</p>
</li>
<li><p><strong>Server</strong>: Listens on <a target="_blank" href="http://localhost:8080"><code>http://localhost:8080</code></a></p>
</li>
</ul>
<p>This covers the full operation of create ToDo item on our ToDo app using native http request. Here is the complete code snippet.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
     <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"gorm.io/driver/mysql"</span>
    <span class="hljs-string">"gorm.io/gorm"</span>
    <span class="hljs-string">"time"</span>
    <span class="hljs-string">"net/http"</span>
    <span class="hljs-string">"encoding/json"</span>


)

<span class="hljs-keyword">var</span> db *gorm.DB <span class="hljs-comment">// Global DB connection</span>

<span class="hljs-keyword">type</span> ToDo <span class="hljs-keyword">struct</span> {
    gorm.Model
    TaskName    <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"not null" json:"title"`</span>
    Description <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"type:text" json:"description"`</span>
    Status      <span class="hljs-keyword">string</span>     <span class="hljs-string">`gorm:"default:NotStarted" json:"status"`</span>
    EndDate     *time.Time <span class="hljs-string">`json:"end_date"`</span>
}

<span class="hljs-comment">// ConnectDB sets up the MySQL connection and assigns it to the global db variable</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ConnectDB</span><span class="hljs-params">()</span></span> {
    dsn := <span class="hljs-string">"root:password@tcp(127.0.0.1:3306)/todo?charset=utf8&amp;parseTime=True&amp;loc=Local"</span>
    dbConn, err := gorm.Open(mysql.Open(dsn), &amp;gorm.Config{})
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"Failed to connect to the database: "</span> + err.Error())
    }
    db = dbConn
}

<span class="hljs-comment">// InitDB initializes the DB and migrates the schema</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">InitDB</span><span class="hljs-params">()</span></span> {
    ConnectDB()
    db.AutoMigrate(&amp;ToDo{}) <span class="hljs-comment">// Creates the 'todos' table based on our struct</span>
}

<span class="hljs-comment">// CreateToDoHandler handles HTTP POST requests to create a new ToDo item</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">ToDoHanlderCreate</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span>{
    <span class="hljs-comment">// step1: Ensure it's a Post request</span>
    <span class="hljs-keyword">if</span> r.Method != http.MethodPost {
     http.Error(w, <span class="hljs-string">"Only Post method is allowed"</span>, http.StatusMethodNotAllowed)
    <span class="hljs-keyword">return</span>
    }
    <span class="hljs-comment">// Step 2: Parse the incoming JSON body into a ToDo struct</span>
    <span class="hljs-keyword">var</span> todo ToDo
    err := json.NewDecoder(r.Body).Decode(&amp;todo)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        http.Error(w, <span class="hljs-string">"Invalid Json Input"</span>, http.StatusBadRequest)
        <span class="hljs-keyword">return</span>
    }
    <span class="hljs-comment">// Step 3: Save the new ToDo item to the database</span>
    db.Create(&amp;todo) <span class="hljs-comment">// Using Gorm Native create object option</span>
    <span class="hljs-comment">// Step 4: Set the content type and return the created object as JSON</span>
    w.Header().Set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(todo)

}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    InitDB() <span class="hljs-comment">// Initialize DB</span>

    <span class="hljs-comment">// Register the route and handler</span>
    http.HandleFunc(<span class="hljs-string">"/todo/create"</span>, ToDoHanlderCreate)

    fmt.Println(<span class="hljs-string">"🚀 Server running at http://localhost:8080"</span>)
    err := http.ListenAndServe(<span class="hljs-string">":8080"</span>, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"Error starting server:"</span>, err)
    }  
}
</code></pre>
<h3 id="heading-how-to-test-the-code">▶️ How to test the code</h3>
<ul>
<li><p>Create Database if you don’t have one by following the steps mentioned <a target="_blank" href="https://claybrainer.com/learning-by-doing-golang-todo-app-part-21-working-with-databases?source=more_series_bottom_blogs#heading-what-youll-need">here</a></p>
</li>
<li><p>Create a file called <code>main.go</code> and copy the above content and paste it.</p>
</li>
<li><p>Initialise your go app by running <code>go mod init example.com/todo</code></p>
</li>
<li><p>Run <code>go mod tidy</code> to download all the dependencies</p>
</li>
<li><p>Finally run <code>go run main.go</code> start your application. You will see the below response on your terminal</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750491157604/76697fdc-ff03-41e8-96cd-39bd18289460.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Now open new terminal and use below curl command to create an entry in your ToDo app</p>
</li>
</ul>
<pre><code class="lang-go">curl -X POST http:<span class="hljs-comment">//localhost:8080/todo/create \</span>
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "title": "Write Go server",
    "description": "Build a simple ToDo API with Go and GORM",
    "status": "InProgress",
    "end_date": "2025-06-30T17:00:00Z"
  }'</span>
</code></pre>
<ul>
<li>You will get the response like this</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750491241640/06b331be-b6df-45fa-a77c-584b9c1d570b.png" alt class="image--center mx-auto" /></p>
<ul>
<li>To check it on database run the below commands</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-comment"># Open CMD and run</span>
docker <span class="hljs-built_in">exec</span> -it mysql /bin/bash
mysql -u root -p
<span class="hljs-comment"># &lt;Enter your password when it asks i.e "password"&gt;</span>
<span class="hljs-comment"># Now you will be in Mysql db</span>
USE todo;
SELECT * FROM to_dos
</code></pre>
<ul>
<li>This command will show you the newly created Entry</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750491399423/7cd5231e-a735-4352-b91e-dad49f7eb804.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-conclusion">📝 <strong>Conclusion</strong></h3>
<p>That wraps up our implementation of a simple ToDo API using Go’s <code>net/http</code> package, GORM, and MySQL. In this post, we created the model, set up the database connection, handled a <code>POST</code> request, and tested it with <code>curl</code>.</p>
<p>In the <strong>next blog post</strong>, we’ll explore alternatives to the <code>net/http</code> standard library—specifically, why developers often choose web frameworks like <strong>Gin</strong> or <strong>Echo</strong> for larger or more maintainable applications. We'll also complete the <strong>Application Layer</strong>, continuing our journey toward building a clean and modular Go backend.</p>
<p>Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Fixing ALB Unhealthy Targets Caused by OpenSearch Restarts]]></title><description><![CDATA[🔍 Before We Begin
Before diving into the actual problem and its solution, let’s take a moment to understand what OpenSearch is and where it's commonly used.
📌 What is OpenSearch? Where is it Used?
OpenSearch is an AWS-managed service based on the o...]]></description><link>https://claybrainer.com/fixing-alb-unhealthy-targets-caused-by-opensearch-restarts</link><guid isPermaLink="true">https://claybrainer.com/fixing-alb-unhealthy-targets-caused-by-opensearch-restarts</guid><category><![CDATA[aws-opensearch-alb-targetgroup]]></category><category><![CDATA[opensearch-with-awsloadbalancer]]></category><category><![CDATA[opensearch]]></category><category><![CDATA[aws-opensearch]]></category><category><![CDATA[alb]]></category><category><![CDATA[aws alb]]></category><category><![CDATA[aws target groups]]></category><category><![CDATA[AWS]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 08 Jun 2025 18:21:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749407763771/a4b48559-bedd-4b11-b84f-cb68c17e3f03.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-before-we-begin">🔍 Before We Begin</h3>
<p>Before diving into the actual problem and its solution, let’s take a moment to understand what OpenSearch is and where it's commonly used.</p>
<h3 id="heading-what-is-opensearch-where-is-it-used">📌 What is OpenSearch? Where is it Used?</h3>
<p><strong>OpenSearch</strong> is an AWS-managed service based on the open-source fork of <strong>Elasticsearch</strong>. It offers powerful capabilities for indexing, searching, and analyzing large volumes of data in near real time.</p>
<p>OpenSearch is widely used in infrastructure for a variety of use cases, including:</p>
<ul>
<li><p>Log analytics</p>
</li>
<li><p>Full-text search</p>
</li>
<li><p>Application performance monitoring</p>
</li>
<li><p>Security information and event management (SIEM)</p>
</li>
</ul>
<p>In recent years, many companies managing their infrastructure on AWS have been shifting towards <strong>AWS-managed services</strong>. This transition helps reduce the operational burden of managing infrastructure, allowing IT teams to focus on solving business-critical problems rather than maintaining and scaling services themselves.</p>
<p><strong>AWS OpenSearch</strong> has become one of the most widely adopted services in this category, providing a fully managed, scalable, and secure alternative to running Elasticsearch on self-managed instances.</p>
<h3 id="heading-how-is-opensearch-configured">⚙️ How is OpenSearch Configured?</h3>
<p>When using <strong>AWS OpenSearch Service</strong>, you typically configure the following key components:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749320030631/a9c9bc5b-4664-4be7-8045-1800ecbd6880.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-exposing-opensearch-to-external-networks-using-alb">🌐 Exposing OpenSearch to External Networks Using ALB</h3>
<p>When you create an <strong>AWS-managed OpenSearch cluster</strong>, AWS provides a domain endpoint that is accessible only from within the <strong>same VPC</strong>, depending on your VPC settings and security group rules. However, <strong>resources or users outside the VPC</strong>—such as developers, third-party services, or monitoring tools—<strong>cannot access this endpoint directly</strong>.</p>
<p>In real-world setups, it's quite common for some users or systems to live <strong>outside the VPC</strong>. For example, accessing the <strong>OpenSearch Dashboard</strong> from an external network becomes difficult since direct connectivity isn’t allowed.</p>
<p>To solve this problem, we can expose OpenSearch <strong>securely through an AWS Application Load Balancer (ALB)</strong>.</p>
<ol>
<li><p><strong>Create an ALB</strong><br /> An <strong>internet-facing ALB</strong> is created to act as a public entry point. This ALB provides a <strong>public DNS name</strong> that external users and resources can access.</p>
</li>
<li><p><strong>Register OpenSearch Node in a Target Group</strong><br /> We then create a <strong>Target Group</strong> and register the <strong>IP address of the OpenSearch node</strong> behind the domain endpoint.</p>
<blockquote>
<p>🔹 <em>Note: Even if your OpenSearch domain has multiple nodes, AWS internally exposes only one node behind the endpoint. Other nodes are used for redundancy and failover.</em></p>
</blockquote>
</li>
<li><p><strong>Connect Target Group to ALB</strong><br /> The Target Group is attached to the ALB. This setup ensures that whenever a request hits the ALB, it is <strong>forwarded to the registered OpenSearch node</strong> in the Target Group.</p>
</li>
<li><p><strong>Route 53 for Domain Management</strong><br /> AWS <strong>Route 53</strong> manages the <strong>public DNS name</strong> of the ALB. If you're using HTTPS, <strong>SSL certificates</strong> issued via ACM (AWS Certificate Manager) are also managed and linked here.</p>
</li>
</ol>
<p>This setup enables <strong>external access to OpenSearch</strong> in a secure and controlled way. The ALB acts as a bridge between the external network and the private OpenSearch cluster inside the VPC.</p>
<h3 id="heading-the-problem-statement-why-the-alb-connection-breaks">⚠️ The Problem Statement: Why the ALB Connection Breaks</h3>
<p>Here comes the real challenge.</p>
<p>In most setups, we <strong>do not maintain static (sticky) IP addresses</strong> for OpenSearch cluster nodes. When an <strong>OpenSearch cluster restarts</strong>—either due to scaling, upgrades, or internal AWS maintenance—<strong>the IP address of the node exposed via the endpoint can change</strong>.</p>
<p>Now, since the <strong>ALB target group is manually registered</strong> with the IP of the previously exposed node, it still tries to forward traffic to that <strong>old, now-invalid IP</strong>. But OpenSearch is now responding from a <strong>new node with a different IP</strong>. And here's the problem:</p>
<ul>
<li><p>The <strong>ALB doesn’t have any native integration</strong> with OpenSearch to update its target group dynamically.</p>
</li>
<li><p>As a result, the <strong>target group points to a stale IP</strong>, and the <strong>target becomes unhealthy</strong>, breaking the connection between the ALB and OpenSearch.</p>
</li>
</ul>
<p>This leads to downtime or failed requests from all external clients relying on the ALB for access.</p>
<h3 id="heading-the-need-for-automation">🔁 The Need for Automation</h3>
<p>To prevent this, we need a <strong>robust, automated mechanism</strong> to <strong>keep the ALB target group in sync</strong> with the currently exposed IP of the OpenSearch endpoint.</p>
<p>There are several ways to implement this, and in the next section, I’ll walk you through <strong>one of the approaches I recently implemented in a production environment.</strong></p>
<h3 id="heading-quick-summary-how-i-solved-the-alb-amp-opensearch-restart-issue">✅ Quick Summary: How I Solved the ALB &amp; OpenSearch Restart Issue</h3>
<p>Before we dive into the detailed steps, here’s a quick overview of how this issue was resolved.</p>
<blockquote>
<p><strong>Assumption:</strong> You already have an <strong>OpenSearch cluster running</strong> and <strong>exposed via an ALB</strong> to allow access from external networks.</p>
</blockquote>
<p>The core idea is to detect when the OpenSearch cluster restarts and automatically <strong>update the ALB target group</strong> with the <strong>currently active OpenSearch node IP</strong>. This ensures that the ALB always routes traffic to a healthy target, avoiding downtime or broken access for external users.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749400844065/1d6cc8e6-f168-4b89-8524-41ceaf255361.png" alt class="image--center mx-auto" /></p>
<p>To achieve this:</p>
<ul>
<li><p>I set up a <strong>CloudWatch alarm</strong> that monitors the number of nodes in the OpenSearch cluster.</p>
</li>
<li><p>Using <strong>EventBridge</strong>, I track when the alarm transitions from an <strong>ALARM</strong> state back to <strong>OK</strong>, indicating that the cluster has recovered after a restart.</p>
</li>
<li><p>This triggers a <strong>Lambda function</strong>, which:</p>
<ul>
<li><p>Fetches the active OpenSearch node IP.</p>
</li>
<li><p><strong>Updates the ALB target group</strong> with the new IP.</p>
</li>
<li><p><strong>Removes any stale IPs</strong> no longer part of the cluster.</p>
</li>
</ul>
</li>
</ul>
<p>This automation ensures that your <strong>ALB always points to a healthy OpenSearch node</strong>, even after a restart — maintaining uninterrupted access for external systems.</p>
<p>In the next section, I’ll walk you through each step of this setup with screenshots and configuration details.</p>
<h3 id="heading-detailed-procedure">🛠️ Detailed Procedure:</h3>
<p>The steps outlined below can be implemented using any Infrastructure-as-Code (IaC) or configuration management tool of your choice. In this guide, the focus is on <strong>what</strong> needs to be implemented rather than <strong>how</strong> it's implemented with a specific tool. You’re free to use the tool or framework that best fits your environment — this guide should help you understand the logic and flow regardless of the platform.</p>
<h4 id="heading-step-1-get-the-total-number-of-nodes-in-your-opensearch-clusteryou-can-find-the-details-in-cloud-watch">🔹 Step 1: Get the Total Number of Nodes in Your OpenSearch ClusterYou can find the details in Cloud watch</h4>
<p>The first step is to identify the <strong>total number of nodes</strong> (both master and data nodes) in your OpenSearch cluster. This information is essential for setting up a reliable CloudWatch alarm.</p>
<p>You can find this detail using <strong>Amazon CloudWatch</strong>:</p>
<ol>
<li><p><strong>Log in</strong> to the <a target="_blank" href="https://console.aws.amazon.com/">AWS Management Console</a><a target="_blank" href="https://console.aws.amazon.com/">.</a></p>
</li>
<li><p>In the search bar, type and select <strong>CloudWatch</strong>.</p>
</li>
<li><p>From the left-hand menu, click on <strong>"All metrics."</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749401245806/e8975351-5c30-4c37-b2ca-183b57274f02.png" alt="claybrainer-Cloudwatch-allmetrics" class="image--center mx-auto" /></p>
<p> In the search bar, type <strong>“ES”</strong> to filter OpenSearch-related metrics, From the results, select <strong>“ES → Per-Domain, Per-Client Metrics.”</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749401441570/379502e1-5e8b-437f-aaf8-f5750dd0281e.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Choose your OpenSearch domain and look for the <strong>Nodes</strong> metric. This will show the number of nodes currently active in your cluster.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749401994622/71ab29ef-1c44-4efe-9afb-da1573f5117e.png" alt class="image--center mx-auto" /></p>
<p> Check the checkbox for <strong>Nodes</strong> corresponding to your specific OpenSearch domain. This will display a graph showing the number of active nodes over time.</p>
</li>
<li><p>While there are certainly easier ways to retrieve the total number of nodes in an OpenSearch cluster, this approach has a key advantage — it allows us to <strong>directly create a CloudWatch alarm</strong> based on this metric.</p>
</li>
<li><p>So, as you might have guessed — the next step is to click on <strong>“Create alarm.”</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749402485185/8c2a4265-a65e-457a-b7fc-453f3b0b2e8e.png" alt class="image--center mx-auto" /></p>
<p> Configure the alarm setting as follow</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749402733663/c125dd55-90a4-4328-a39b-05823ce37488.png" alt class="image--center mx-auto" /></p>
<p>In the next step, click <strong>“Remove”</strong> under the <strong>notification section</strong> to delete any default SNS notification settings — since we’ll be using <strong>EventBridge</strong> to trigger the action instead of relying on SNS alerts.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749403061663/2c9c8618-8a73-4f91-8330-1fb0b870d309.png" alt class="image--center mx-auto" /></p>
<p>Add your preferred name to the Alarm</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749403175017/983b81a0-96a4-4636-8612-0282c8a6c5c5.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Finally Click on <strong>Create Alarm</strong></p>
</li>
<li><p>Now, follow <strong>Steps 3 to 5</strong> in the alarm creation flow. Once completed, you should see <strong>1 alarm</strong> listed under your OpenSearch domain name in the <strong>CloudWatch metrics dashboard</strong>.</p>
</li>
<li><p>Select the <strong>Node</strong> Checkbox, and then select the <strong>Graphed Metrics</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749403958265/0ca4cd64-20aa-422c-8adc-6885519f5947.png" alt class="image--center mx-auto" /></p>
<p>Select <strong>Add Math » Conditional » Equals</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749404505077/b36dc7cd-1a70-4c6c-a714-8151ff5418bb.png" alt class="image--center mx-auto" /></p>
<p>From the expression click <strong>Edit icon</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749404610653/db5e648a-0622-4fac-a88c-027fab2e0b15.png" alt class="image--center mx-auto" /></p>
<p>Change the value to <code>m1 == &lt;your total number of nodes&gt;</code>, where <code>m1</code> refers to the metric ID for <strong>Nodes</strong> in your alarm, which you can find in the same menu under <strong>ID</strong>, and <code>&lt;your total number of nodes&gt;</code> is the value you noted in <strong>Step 8</strong>. Click on <strong>Apply</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749404680708/1090ab5f-0327-4de1-82af-28afe0142743.png" alt class="image--center mx-auto" /></p>
<p><strong>Voila!</strong> The alarm is now set. It will return a value of <strong>1</strong> when the current node count is exactly equal to the expected total (in our case, <strong>6</strong>), and <strong>0</strong> when it’s either greater or less than that.</p>
</li>
<li><p>This is exactly what we need — because any change in node count usually indicates that something has happened with the OpenSearch cluster (such as a restart or scaling event). At this point, we need to verify the ALB <strong>Target Group IP</strong> and update it if it no longer points to a healthy OpenSearch node.</p>
</li>
<li><p>Now, let’s move on to setting up <strong>EventBridge</strong> to trigger a <strong>Lambda function</strong> whenever this happens.</p>
</li>
</ol>
<h4 id="heading-step-2-configure-a-lambda-function-to-update-the-target-group-with-the-opensearch-endpoint-node-ip">🔹 Step 2: Configure a Lambda Function to Update the Target Group with the OpenSearch Endpoint Node IP</h4>
<p>There are plenty of resources — including official documentation and YouTube tutorials — that can guide you through <strong>how to create and configure a Lambda function</strong>.</p>
<p>In this section, I’ll focus on what matters most for this use case:</p>
<ul>
<li><p>The <strong>runtime/language</strong> you should select for your Lambda. - <strong>Python:3.9 and above</strong></p>
</li>
<li><p>The <strong>script</strong> you’ll use to update the Target Group.</p>
</li>
<li><p>The <strong>environment variables</strong> that need to be configured for the Lambda to work effectively.</p>
<ul>
<li><p><strong>OPENSEARCH_HOST:</strong></p>
<ul>
<li><p>You can get this value from, Login in to console » Search <strong>Opensearch</strong> » Click <strong>Domains</strong> » Select your <strong>Domain</strong> » You can find this value under <strong>VPC EndPoint</strong></p>
</li>
<li><p><strong>(Remove “https://” when adding it in env variable)</strong></p>
</li>
</ul>
</li>
<li><p><strong>TARGET_GROUP_ARN</strong></p>
<ul>
<li>You can get this value from Login in to console »Search <strong>Target Group</strong> » Select your <strong>Target Group</strong> » You can find this value under <strong>ARN</strong></li>
</ul>
</li>
</ul>
</li>
<li><p>Script to use</p>
<blockquote>
<p>Note: I’ve hard coded the port to 443 replace it with your port number</p>
</blockquote>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> socket

elb = boto3.client(<span class="hljs-string">'elbv2'</span>)

TARGET_GROUP_ARN = os.environ[<span class="hljs-string">'TARGET_GROUP_ARN'</span>]
OPENSEARCH_HOST = os.environ[<span class="hljs-string">'OPENSEARCH_HOST'</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">try</span>:
        <span class="hljs-comment"># Resolve new IP of OpenSearch</span>
        new_ip = socket.gethostbyname(OPENSEARCH_HOST)
        print(<span class="hljs-string">f"Resolved OpenSearch IP: <span class="hljs-subst">{new_ip}</span>"</span>)

        <span class="hljs-comment"># Describe current targets</span>
        current_targets = elb.describe_target_health(TargetGroupArn=TARGET_GROUP_ARN)
        registered_ips = [t[<span class="hljs-string">'Target'</span>][<span class="hljs-string">'Id'</span>] <span class="hljs-keyword">for</span> t <span class="hljs-keyword">in</span> current_targets[<span class="hljs-string">'TargetHealthDescriptions'</span>]]
        print(<span class="hljs-string">f"Currently registered IPs: <span class="hljs-subst">{registered_ips}</span>"</span>)

        <span class="hljs-comment">#Deregister old IPs</span>
        <span class="hljs-keyword">for</span> ip <span class="hljs-keyword">in</span> registered_ips:
            <span class="hljs-keyword">if</span> ip != new_ip:
                print(<span class="hljs-string">f"Deregistering IP: <span class="hljs-subst">{ip}</span>"</span>)
                elb.deregister_targets(
                    TargetGroupArn=TARGET_GROUP_ARN,
                    Targets=[{<span class="hljs-string">"Id"</span>: ip, <span class="hljs-string">"Port"</span>: <span class="hljs-number">443</span>}]
                )

        <span class="hljs-comment"># Register new IP if not already registered</span>
        <span class="hljs-keyword">if</span> new_ip <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> registered_ips:
            print(<span class="hljs-string">f"Registering new IP: <span class="hljs-subst">{new_ip}</span>"</span>)
            elb.register_targets(
                TargetGroupArn=TARGET_GROUP_ARN,
                Targets=[{<span class="hljs-string">"Id"</span>: new_ip, <span class="hljs-string">"Port"</span>: <span class="hljs-number">443</span>}]
            )

        <span class="hljs-keyword">return</span> {<span class="hljs-string">"status"</span>: <span class="hljs-string">"Success"</span>, <span class="hljs-string">"new_ip"</span>: new_ip}

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"Error: <span class="hljs-subst">{str(e)}</span>"</span>)
        <span class="hljs-keyword">raise</span> e
</code></pre>
<ul>
<li><p>Make sure you have necessary permission for this Lambda function to, if you don’t have create an IAM role with below access.</p>
<ul>
<li><p>Read the VPC Endpoint configuration settings from Opensearch</p>
</li>
<li><p>Ability to add Targets to the Target group</p>
</li>
<li><p>Ability to Selete/Drain targets from the target group</p>
</li>
</ul>
</li>
<li><p>Once all are set test it manually invoking the lambda function</p>
</li>
</ul>
<h4 id="heading-step-3-configuring-event-bridge-to-trigger-lambda">🔹 Step 3: Configuring Event Bridge to trigger Lambda</h4>
<ol>
<li><p><strong>Log in</strong> to the <a target="_blank" href="https://console.aws.amazon.com/">AWS Management Console</a><a target="_blank" href="https://console.aws.amazon.com/">.</a></p>
</li>
<li><p>In the search bar, type and select <strong>Amazon</strong> <strong>EventBridge »</strong> From left menu Click <strong>Rules »</strong> Select <strong>Create rule</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749405246870/f9d9640c-c2cd-48be-8df9-3e4ef7b4ef00.png" alt class="image--center mx-auto" /></p>
<p> Give preferred rule name and description and Click <strong>Next</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749405317120/2021c550-cc2a-402b-9ad7-e10a9c51e0fa.png" alt class="image--center mx-auto" /></p>
<p> Click <strong>Edit Pattern</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749405384494/56d6e023-4450-4f30-be9c-cc201d882d97.png" alt class="image--center mx-auto" /></p>
<p> Replace the content what ever needed as mentioned below and paste the content in the code snippet.</p>
<p> - YOUR ALARM NAME » Replace this with the Alarm name which you created in Step 1</p>
</li>
</ol>
<pre><code class="lang-json">{
  <span class="hljs-attr">"source"</span>: [<span class="hljs-string">"aws.cloudwatch"</span>],
  <span class="hljs-attr">"detail-type"</span>: [<span class="hljs-string">"CloudWatch Alarm State Change"</span>],
  <span class="hljs-attr">"detail"</span>: {
    <span class="hljs-attr">"alarmName"</span>: [<span class="hljs-string">"&lt;YOUR ALARM NAME&gt;"</span>],
    <span class="hljs-attr">"state"</span>: {
      <span class="hljs-attr">"value"</span>: [<span class="hljs-string">"ALARM"</span>, <span class="hljs-string">"OK"</span>]
    },
    <span class="hljs-attr">"previousState"</span>: {
      <span class="hljs-attr">"value"</span>: [<span class="hljs-string">"OK"</span>, <span class="hljs-string">"ALARM"</span>]
    }
  }
}
</code></pre>
<ol start="6">
<li><p>Explanation of the condition</p>
<ol>
<li><p><code>"source": ["aws.cloudwatch"]</code><br /> Ensures the event is coming from <strong>Amazon CloudWatch</strong>.</p>
</li>
<li><p><code>"detail-type": ["CloudWatch Alarm State Change"]</code><br /> Captures only events related to alarm state changes.</p>
</li>
<li><p><code>"alarmName": ["&lt;YOUR ALARM NAME&gt;"]</code><br /> Filters the event to trigger <strong>only for the specific alarm</strong> you created to monitor OpenSearch node count.</p>
</li>
<li><p><code>"state": { "value": ["ALARM", "OK"] }</code><br /> Triggers the rule when the alarm <strong>enters from the ALARM to OK state</strong>.</p>
</li>
<li><p><code>"previousState": { "value": ["OK", "ALARM"] }</code><br /> Ensures the transition is meaningful — for example, from OK ➝ ALARM and then ALARM ➝ OK — avoiding unnecessary triggers.</p>
</li>
</ol>
</li>
<li><p>Click <strong>Next</strong> to save your pattern</p>
</li>
<li><p>Then For the target</p>
<ol>
<li><p>Target Type : AWS Service</p>
</li>
<li><p>Select a target » Lambda Function</p>
</li>
<li><p>Function » Select the function you created on Step 2 from drop down</p>
</li>
<li><p>Leave the other box as it is and Click <strong>Next</strong></p>
</li>
</ol>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1749405855569/26828033-1829-4e4f-b890-a64fcbd6bde6.png" alt class="image--center mx-auto" /></p>
<ol start="9">
<li>In the next step add the necessary tag if required and <strong>click Next</strong> and Finally review the configuration and click on <strong>Create Rule</strong></li>
</ol>
<p><strong>Voila!</strong> This concludes the overall configuration for automating the update of OpenSearch Node IPs in the Target Group.</p>
<p>With this setup in place, your <strong>Target Group will always stay up to date</strong> with the <strong>active and healthy IP address</strong> of your OpenSearch cluster — ensuring seamless connectivity even during restarts or node changes.</p>
]]></content:encoded></item></channel></rss>