<?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>Mon, 18 May 2026 03:48:02 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[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><item><title><![CDATA[Learning By Doing Golang - ToDo App Part 2.2 - Creating Database Layer]]></title><description><![CDATA[So far, we’ve discussed why our app needs a database, the various ways to connect a database from Golang, and how to choose the most suitable method for our project.If you’re just joining the series or missed the earlier discussion on why a database ...]]></description><link>https://claybrainer.com/learning-by-doing-golang-todo-app-part-2-2-creating-database-layer</link><guid isPermaLink="true">https://claybrainer.com/learning-by-doing-golang-todo-app-part-2-2-creating-database-layer</guid><category><![CDATA[golang]]></category><category><![CDATA[todoapp]]></category><category><![CDATA[learningbydoing]]></category><category><![CDATA[golang-database]]></category><category><![CDATA[learning-golang]]></category><category><![CDATA[golang-todo]]></category><category><![CDATA[golang-gorm]]></category><category><![CDATA[golang-orm]]></category><category><![CDATA[learning-by-doing-golang]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sat, 31 May 2025 07:16:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748675857230/a8394c92-c75c-4550-a9ed-a5485fbcf84b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So far, we’ve discussed why our app needs a database, the various ways to connect a database from Golang, and how to choose the most suitable method for our project.<br />If you’re just joining the series or missed the earlier discussion on why a database connection is necessary and why we chose a specific approach, please check out the previous articles <a target="_blank" href="https://claybrainer.com/series/learning-by-doing-golang">[link]</a>.</p>
<h3 id="heading-building-the-database-layer-for-our-todo-app">🧱 Building the Database Layer for Our ToDo App</h3>
<p>With everything we’ve learned so far, let’s go ahead and build the <strong>database layer</strong> for our ToDo app.</p>
<p>Before we dive into the implementation, let’s outline the operations we’ll be handling through our database layer. This will help us clearly identify what we need to build:</p>
<h4 id="heading-what-will-the-database-layer-do">🧩 What Will the Database Layer Do?</h4>
<ol>
<li><p>We need to <strong>establish connection</strong> with the Database.</p>
</li>
<li><p><strong>Define the schema</strong> (i.e., table structure) to store our ToDo items</p>
</li>
<li><p><strong>Implement CRUD operations</strong> (Create, Read, Update, Delete) such as:</p>
<ul>
<li><p>Create a new ToDo</p>
</li>
<li><p>Update an existing ToDo</p>
</li>
<li><p>Get all ToDos</p>
</li>
<li><p>Get a specific ToDo by ID</p>
</li>
<li><p>Delete a ToDo</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-recap-of-key-decisions">🔁 Recap of Key Decisions</h4>
<p>Before we begin, here’s a quick recap of what we’ve decided so far. If you missed any part, feel free to check the previous articles:</p>
<ol>
<li><p>We’re using <strong>MySQL</strong> as our database of choice</p>
</li>
<li><p>We’ll use <strong>Golang with the GORM library</strong>, along with the <strong>MySQL driver</strong>, to interact with the database</p>
</li>
</ol>
<p>✅ With all that clear, let’s start putting things together and build the database layer!</p>
<h3 id="heading-establishing-connection-with-our-db">Establishing Connection with our DB:</h3>
<p>Let’s start with establishing connection with our DB</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-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 
}
</code></pre>
<h3 id="heading-lets-define-schema">Lets Define Schema:</h3>
<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-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">// This will initialize DB and create Schema (Our Table structure)</span>
<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{})
}
</code></pre>
<p>We are defining schema i.e (Table structure) for our database in the form of Struct. Let deep dive what we have entered.</p>
<ul>
<li><p><code>gorm.Model</code></p>
<p>  This is a built-in struct in GORM that automatically includes four common fields:</p>
<ul>
<li><ul>
<li><p><code>ID</code> – a unique identifier for each record</p>
<p>    * <code>CreatedAt</code> – timestamp of when the record was created</p>
<p>    * <code>UpdatedAt</code> – timestamp of the last update</p>
<p>    * <code>DeletedAt</code> – soft delete timestamp</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>    By embedding <code>gorm.Model</code> at the start of your struct, GORM auto-generates and manages these fields for each record in the table.</p>
<ul>
<li><p><code>TaskName string `gorm:"not null" json:"title"` </code></p>
<ul>
<li><p><code>TaskName</code>: This is a field in your struct that will map to a column in your database table. It stores the task name for each ToDo item.</p>
</li>
<li><p><code>string</code>: The data type of the field.</p>
</li>
<li><p><code>gorm:"not null"</code>: A GORM tag that enforces the column to never be null in the database.</p>
</li>
<li><p><code>json:"title"</code>: This tag tells Go how to map this field when converting JSON data, such as when handling API requests and responses. So, if you send or receive JSON with the key <code>title</code>, it will map to <code>TaskName</code> in your Go struct. We will see how this is used when we are testing the app.</p>
</li>
</ul>
</li>
<li><p><code>Status string gorm:"default:NotStarted" json:"status" validate:"required,oneof=NotStarted InProgress Pending Completed"</code></p>
<ul>
<li><p><code>Status</code>: is a column in your database table used to track the current state of a ToDo item.</p>
</li>
<li><p>It’s of type <code>string</code>.</p>
</li>
<li><p><code>json:"status"</code> : This field is same as json:title used when passing JSON using API</p>
</li>
<li><p><code>validate:"required,oneof=NotStarted InProgress Pending Completed"</code> : This field defines that this variable is required and its value should be one of “<code>NotStarted InProgress Pending Completed</code>"</p>
</li>
<li><p><code>default:NotStarted</code>: If no value is defined we are setting default value as NotStarted</p>
</li>
</ul>
</li>
<li><p>Since EndDate variable type should be handled in time we mentioned it as <code>*time.Time</code> type</p>
</li>
</ul>
<h3 id="heading-implement-curd-operation">Implement CURD Operation</h3>
<p>As we defined the Schema in the above step lets complete all the operation required for our ToDo App</p>
<p>Operation Required</p>
<ul>
<li><p>Create a new ToDo</p>
</li>
<li><p>Update an existing ToDo</p>
</li>
<li><p>Get all ToDos</p>
</li>
<li><p>Get a specific ToDo by ID</p>
</li>
<li><p>Delete a ToDo</p>
</li>
</ul>
<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-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">// This will initialize DB and create Schema (Our Table structure)</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-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">// GetToDo returns a list of all ToDo items from the database.</span>
<span class="hljs-comment">// This function does not take any input and returns a slice of ToDo structs ([]ToDo).</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetToDo</span><span class="hljs-params">()</span> []<span class="hljs-title">ToDo</span></span> {
    <span class="hljs-keyword">var</span> todos []ToDo <span class="hljs-comment">// Declare a variable to hold the fetched ToDo items (slice of ToDo structs)</span>
    <span class="hljs-comment">// Use GORM's Find method to retrieve all records from the ToDo table and store them in variable 'todos'</span>
    db.Find(&amp;todos)
    <span class="hljs-comment">// Return the fetched ToDo items</span>
    <span class="hljs-keyword">return</span> todos
}

<span class="hljs-comment">// GetToDoByID fetches a specific ToDo item from the database based on the provided ID.</span>
<span class="hljs-comment">// It takes an integer ID as input and returns a pointer to a ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetToDoByID</span><span class="hljs-params">(ID <span class="hljs-keyword">int64</span>)</span> *<span class="hljs-title">ToDo</span></span> {
    <span class="hljs-comment">// We declare a single instance of the ToDo struct (not a slice),</span>
    <span class="hljs-comment">// because we're expecting only one record, not a list of ToDos.</span>
    <span class="hljs-keyword">var</span> todo ToDo
    <span class="hljs-comment">// Use GORM's First method to retrieve the first matching record by ID.</span>
    <span class="hljs-comment">// "id = ?" is a query condition where the placeholder (?) gets replaced by the ID value.</span>
    db.First(&amp;todo, <span class="hljs-string">"id = ?"</span>, ID)
    <span class="hljs-comment">// Return a pointer to the fetched ToDo item</span>
    <span class="hljs-keyword">return</span> &amp;todo
}

<span class="hljs-comment">// DeleteToDo removes a ToDo item from the database based on the provided ID.</span>
<span class="hljs-comment">// It takes an integer ID as input and returns the deleted ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DeleteToDo</span><span class="hljs-params">(ID <span class="hljs-keyword">int64</span>)</span> <span class="hljs-title">ToDo</span></span> {
    <span class="hljs-comment">// We declare a single instance of the ToDo struct (not a slice),</span>
    <span class="hljs-comment">// because we're expecting only one record, not a list of ToDos.</span>
    <span class="hljs-keyword">var</span> todo ToDo <span class="hljs-comment">// Declare a variable to hold the ToDo item we want to delete</span>

    <span class="hljs-comment">// Use GORM's Delete method to remove the record from the database using the given ID.</span>
    <span class="hljs-comment">// The first argument is the model type, the second is the ID used for deletion.</span>
    db.Delete(&amp;todo, ID)

    <span class="hljs-comment">// Return the todo struct (note: this will only contain ID as GORM does not fetch the record before deletion)</span>
    <span class="hljs-keyword">return</span> todo
}

<span class="hljs-comment">// UpdateToDo updates an existing ToDo item in the database.</span>
<span class="hljs-comment">// It takes a pointer to a ToDo struct (with updated fields) as input and returns the updated ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UpdateToDo</span><span class="hljs-params">(todo *ToDo)</span> *<span class="hljs-title">ToDo</span></span> {

    <span class="hljs-comment">// Use GORM's Save method to update the existing record.</span>
    <span class="hljs-comment">// Save will update the record if it exists (based on the primary key in the struct).</span>
    <span class="hljs-comment">// When using db.Save(todo), you must pass all the field values — both updated and unchanged.</span>
    <span class="hljs-comment">// This is because Save performs a full update, replacing the entire row in the database.</span>
    db.Save(todo)

    <span class="hljs-comment">// Return the updated ToDo item</span>
    <span class="hljs-keyword">return</span> todo
}
</code></pre>
<p>✅ <strong>Database Layer Completed</strong></p>
<p>We’ve now completed all the essential database operations (CRUD) for our ToDo app. This wraps up our <strong>Database Layer Setup</strong>! 🎉</p>
<p>Your code so far should include:</p>
<ul>
<li><p>Database connection setup using GORM</p>
</li>
<li><p>ToDo model definition</p>
</li>
<li><p>Functions for Create, Read (all &amp; by ID), Update, and Delete operations</p>
</li>
</ul>
<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-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">// GetToDo returns a list of all ToDo items from the database.</span>
<span class="hljs-comment">// This function does not take any input and returns a slice of ToDo structs ([]ToDo).</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetToDo</span><span class="hljs-params">()</span> []<span class="hljs-title">ToDo</span></span> {
    <span class="hljs-keyword">var</span> todos []ToDo <span class="hljs-comment">// Declare a variable to hold the fetched ToDo items (slice of ToDo structs)</span>
    <span class="hljs-comment">// Use GORM's Find method to retrieve all records from the ToDo table and store them in variable 'todos'</span>
    db.Find(&amp;todos)
    <span class="hljs-comment">// Return the fetched ToDo items</span>
    <span class="hljs-keyword">return</span> todos
}

<span class="hljs-comment">// GetToDoByID fetches a specific ToDo item from the database based on the provided ID.</span>
<span class="hljs-comment">// It takes an integer ID as input and returns a pointer to a ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetToDoByID</span><span class="hljs-params">(ID <span class="hljs-keyword">int64</span>)</span> *<span class="hljs-title">ToDo</span></span> {
    <span class="hljs-comment">// We declare a single instance of the ToDo struct (not a slice),</span>
    <span class="hljs-comment">// because we're expecting only one record, not a list of ToDos.</span>
    <span class="hljs-keyword">var</span> todo ToDo
    <span class="hljs-comment">// Use GORM's First method to retrieve the first matching record by ID.</span>
    <span class="hljs-comment">// "id = ?" is a query condition where the placeholder (?) gets replaced by the ID value.</span>
    db.First(&amp;todo, <span class="hljs-string">"id = ?"</span>, ID)
    <span class="hljs-comment">// Return a pointer to the fetched ToDo item</span>
    <span class="hljs-keyword">return</span> &amp;todo
}

<span class="hljs-comment">// DeleteToDo removes a ToDo item from the database based on the provided ID.</span>
<span class="hljs-comment">// It takes an integer ID as input and returns the deleted ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">DeleteToDo</span><span class="hljs-params">(ID <span class="hljs-keyword">int64</span>)</span> <span class="hljs-title">ToDo</span></span> {
    <span class="hljs-comment">// We declare a single instance of the ToDo struct (not a slice),</span>
    <span class="hljs-comment">// because we're expecting only one record, not a list of ToDos.</span>
    <span class="hljs-keyword">var</span> todo ToDo <span class="hljs-comment">// Declare a variable to hold the ToDo item we want to delete</span>

    <span class="hljs-comment">// Use GORM's Delete method to remove the record from the database using the given ID.</span>
    <span class="hljs-comment">// The first argument is the model type, the second is the ID used for deletion.</span>
    db.Delete(&amp;todo, ID)

    <span class="hljs-comment">// Return the todo struct (note: this will only contain ID as GORM does not fetch the record before deletion)</span>
    <span class="hljs-keyword">return</span> todo
}

<span class="hljs-comment">// UpdateToDo updates an existing ToDo item in the database.</span>
<span class="hljs-comment">// It takes a pointer to a ToDo struct (with updated fields) as input and returns the updated ToDo struct.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UpdateToDo</span><span class="hljs-params">(todo *ToDo)</span> *<span class="hljs-title">ToDo</span></span> {

    <span class="hljs-comment">// Use GORM's Save method to update the existing record.</span>
    <span class="hljs-comment">// Save will update the record if it exists (based on the primary key in the struct).</span>
    <span class="hljs-comment">// When using db.Save(todo), you must pass all the field values — both updated and unchanged.</span>
    <span class="hljs-comment">// This is because Save performs a full update, replacing the entire row in the database.</span>
    db.Save(todo)

    <span class="hljs-comment">// Return the updated ToDo item</span>
    <span class="hljs-keyword">return</span> todo
}
</code></pre>
<p>🚀 <strong>What’s Next?</strong></p>
<p>In the next section, we’ll focus on building the <strong>Application Layer (Middle Layer)</strong> — the core part of our app logic. We’ll learn how to:</p>
<ul>
<li><p>Connect the DB layer with our application logic</p>
</li>
<li><p>Handle HTTP requests</p>
</li>
<li><p>Build out our API endpoints</p>
</li>
</ul>
<p>Stay tuned as we start connecting all the dots and bring our ToDo app to life! 🔗✨</p>
]]></content:encoded></item><item><title><![CDATA[Learning By Doing Golang - ToDo App Part 2.1 - Working with Databases]]></title><description><![CDATA[In this article, we’ll explore the various options available in Golang to interact with a database. We’ll evaluate each approach and identify the most suitable solution for our ToDo app. Once we select the best option, we’ll dive into how it works an...]]></description><link>https://claybrainer.com/learning-by-doing-golang-todo-app-part-21-working-with-databases</link><guid isPermaLink="true">https://claybrainer.com/learning-by-doing-golang-todo-app-part-21-working-with-databases</guid><category><![CDATA[golang-database]]></category><category><![CDATA[learning-golang]]></category><category><![CDATA[golang-todo]]></category><category><![CDATA[golang-gorm]]></category><category><![CDATA[golang-orm]]></category><category><![CDATA[learning-by-doing-golang]]></category><category><![CDATA[learningbydoing]]></category><category><![CDATA[golang]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 25 May 2025 17:01:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748192407004/9ff41305-ebb6-4176-b5c7-65f6d2b421af.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we’ll explore the various options available in Golang to interact with a database. We’ll evaluate each approach and identify the most suitable solution for our ToDo app. Once we select the best option, we’ll dive into how it works and implement it as the first step in building our application.</p>
<p>We’ve already discussed <strong>why a database is essential</strong> for our app and <strong>why we chose a SQL database</strong> for this project. If you’re just joining us, you can catch up on that <a target="_blank" href="https://claybrainer.com/learning-by-doing-golang-todo-app-01#heading-design-and-requirements">here</a>.</p>
<h2 id="heading-prerequisites">⚙️ Prerequisites</h2>
<p>Before we start building, let’s set up the basic infrastructure for this topic.</p>
<h3 id="heading-what-youll-need">✅ What You’ll Need:</h3>
<ul>
<li><p><strong>Docker</strong> installed on your machine:</p>
<ul>
<li><p>Use regular <strong>Docker</strong> on Linux</p>
</li>
<li><p>Use <strong>Docker Desktop</strong> on Windows or macOS</p>
</li>
<li><p>Make sure Docker is <strong>installed and running</strong> on your system.</p>
</li>
</ul>
</li>
<li><p>Once it’s up, run the following command to <strong>spin up a MySQL container</strong> that we’ll use in our project.</p>
</li>
<li><pre><code class="lang-bash">  docker run --name mysql -d \
    -e MYSQL_ROOT_PASSWORD=password \
    -e MYSQL_DATABASE=todo \
    -p 3306:3306 \
    mysql:latest
</code></pre>
</li>
<li><p>This will start a MySQL container with:</p>
<ul>
<li><p><strong>Database Name:</strong> <code>todo</code></p>
</li>
<li><p><strong>Root Password:</strong> <code>password</code></p>
</li>
<li><p><strong>Port:</strong> <code>3306</code> (exposed to your local machine)</p>
<blockquote>
<p>Make sure port 3306 is not already in use. If it is, update the port accordingly.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h2 id="heading-lets-start-exploring-the-database">🧭 Let’s Start Exploring the Database</h2>
<p>Before we dive into code, let’s clarify what exactly we’ll be doing when working with the <strong>database from our Golang application</strong>.</p>
<p>Here are the key operations we’ll perform:</p>
<ol>
<li><p><strong>Establish a connection</strong> between our Go app and the MySQL database</p>
</li>
<li><p><strong>Create the database schema</strong> — defining the table structure (rows and columns) for our ToDo items</p>
</li>
<li><p><strong>Perform CRUD operations</strong> using Go:</p>
<ul>
<li><p><strong>Create</strong> new ToDos</p>
</li>
<li><p><strong>Read</strong> existing ToDos</p>
</li>
<li><p><strong>Update</strong> ToDos</p>
</li>
<li><p><strong>Delete</strong> ToDos</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-establishing-connection-to-the-database">🔗 Establishing Connection to the Database</h3>
<p>To connect our Go application to a database, we need to use a <strong>library</strong> that enables communication between the two. Go offers multiple options for this—both <strong>native libraries</strong> and <strong>third-party libraries</strong> built by the developer community.</p>
<p>we’ll start by exploring how to connect to a database using <strong>Go’s native</strong> <code>database/sql</code> package. This standard library provides a generic interface for interacting with SQL databases. We will look at its advantages and limitations and then we will move towards more suitable options.</p>
<h3 id="heading-database-connection-with-go-native-library">🧩 Database Connection with Go Native Library</h3>
<p>To establish a connection between our Go application and a MySQL database, we need <strong>three essential components</strong>. Let’s walk through each one:</p>
<ul>
<li><p>Importing required library. We start by importing Go’s standard SQL library: <code>database/sql</code>. This package provides a generic interface for working with SQL databases. However, since it's <strong>database-agnostic</strong>, we need to tell Go which specific database we're using.</p>
</li>
<li><p>Add the MySQL Driver. To work with MySQL specifically, we import the driver: <code>_ "github.com/go-sql-driver/mysql"</code></p>
<blockquote>
<p>💡 <strong>How do you know to use</strong> <code>_</code>?<br />Simple—just Google! Look up <code>golang mysql database/sql</code> and read the official docs or examples. This approach works not just here, but for any library or tool you use. You can try it for this as well. Give a try!!!</p>
</blockquote>
</li>
<li><p>Third things is <strong>DSN(DataSourceName).</strong> The <strong>DSN</strong> tells Go about connection details of database. It includes:</p>
<ul>
<li><p>Database username</p>
</li>
<li><p>Database Password</p>
</li>
<li><p>What protocol it is using to connect</p>
</li>
<li><p>What is the database server url and Port</p>
</li>
<li><p>Database Name</p>
</li>
<li><p>Example :- <code>dsn := "root:password@tcp(127.0.0.1:3306)/mysql?parseTime=true"</code></p>
<ul>
<li><code>?parseTime=true</code> Defines Go to parse the date time in time.Time format rather byte or string format.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"database/sql"</span>
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"time"</span>
    _ <span class="hljs-string">"github.com/go-sql-driver/mysql"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Setting DataSourceName(DSN) Details. "?parseTime=true" Defines Go to parse the date time in time.Time format rather byte or string format.</span>
    <span class="hljs-comment">// By default it parse in string, by using the "go-sql-driver/mysql" in build capability we are telling go to format the time in time.Time format so that we can use it anywhere in the program</span>
    dsn := <span class="hljs-string">"root:password@tcp(127.0.0.1:3306)/mysql?parseTime=true"</span>
    db, err := sql.Open(<span class="hljs-string">"mysql"</span>, dsn)
    <span class="hljs-comment">// Error Handling</span>
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err) <span class="hljs-comment">// Stop the program immediately with give the error message</span>
    }
    <span class="hljs-keyword">defer</span> db.Close() <span class="hljs-comment">// This make sure that all the db connection is closed once main exit no matter how it exit</span>
    err = db.Ping() <span class="hljs-comment">// We are trying to ping the DB to check DB is reachable. Not required in real world, use it for our demo</span>
    <span class="hljs-comment">// Error Handling    </span>
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    fmt.Println(<span class="hljs-string">"Connected Successfully"</span>)

    <span class="hljs-comment">// Declaring the variable to store the result</span>
    <span class="hljs-keyword">var</span> Id <span class="hljs-keyword">int</span>
    <span class="hljs-keyword">var</span> Zone <span class="hljs-keyword">string</span>
    <span class="hljs-comment">// Here QueryRow is part of our "go-sql-driver/mysql" library.</span>
    <span class="hljs-comment">// what we are saying here is Select 1 row(which is SQL Command) and Scan will capture the output then we ask go to store result in Memory Location(&amp;) of testValue variable. So that the actual variable get updated instead of a copy of variable</span>
    err = db.QueryRow(<span class="hljs-string">"SELECT * FROM time_zone_name LIMIT 1"</span>).Scan(&amp;Zone, &amp;Id)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(err)
    }
    fmt.Println(<span class="hljs-string">"Test Query Result:"</span>, Id, Zone)
}
</code></pre>
<p>You can read through the code above, where I’ve added detailed comments explaining what each line does and why it’s used. This will help you understand the flow and purpose of each part of the connection process.</p>
<p>Once you're comfortable with the code, you can <strong>run it to test the database connection</strong>.</p>
<ul>
<li><p>Create a file called main.go</p>
</li>
<li><p><strong>Type the complete code</strong> into <code>main.go</code>. Make sure all imports and logic are included as discussed earlier.</p>
</li>
<li><p><strong>Open your terminal</strong> in the project directory and run the following commands:</p>
<ul>
<li><p><code>go mod init example.com/test-db</code> - This initializes your Go module.</p>
</li>
<li><p><code>go mod tidy</code> - This downloads and adds all the required libraries and dependencies (like the MySQL driver).</p>
</li>
<li><p><code>go run main.go</code> - This compiles and runs your code.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-limitations-of-go-native-library">Limitations of go native library</h3>
<ul>
<li><p><strong>Manual SQL Queries:</strong> You must write full SQL queries yourself, even for simple operations.</p>
</li>
<li><p><strong>Error Handling Overhead:</strong> Requires explicit error checking after every DB operation.</p>
</li>
<li><p><strong>Manual Resource Management:</strong> You have to close rows and connections manually to avoid leaks.</p>
</li>
<li><p><strong>Limited Productivity Features:</strong> Offers no tools for validation, relationships, or query building.</p>
</li>
<li><p><strong>Repetitive boilerplate :</strong> We are forced to repeat the query multiple times at multiple location.</p>
</li>
<li><p>And More</p>
</li>
</ul>
<p>To overcome these issues, developers often use third-party libraries that provide a more developer-friendly experience—like GORM, sqlx, or ent. Lets explore what it is</p>
<h3 id="heading-introduction-to-orm-object-relational-mapping">🚀 Introduction to ORM (Object Relational Mapping):</h3>
<p>ORM stands for <strong>Object Relational Mapping</strong>—a technique that allows developers to interact with the database using <strong>regular Go code instead of raw SQL</strong>. The ORM internally converts that code into SQL queries and executes them on your behalf.</p>
<p>One of the most popular and widely used ORMs in the Go ecosystem is <a target="_blank" href="https://gorm.io/">GORM</a>. It simplifies database operations, reduces boilerplate code, and makes your codebase cleaner and easier to maintain—especially as your project grows.</p>
<h3 id="heading-database-connection-using-gorm">🧩 Database Connection using GORM:</h3>
<p>Establishing connection to DB with GORM also follow the same three steps which we discussed in previous section. Only thing which changes is we import GORM library here</p>
<ul>
<li><p>Importing gorm library <a target="_blank" href="http://gorm.io/gorm"><code>gorm.io/gorm</code></a></p>
</li>
<li><p>Importing gorm sql diriver as we are using sql database <code>gorm.io/driver/mysql</code></p>
</li>
<li><p>DSN Connection details same as native database/sql connection. <code>dsn := "root:password@tcp(127.0.0.1:3306)/mysql?charset=utf8&amp;parseTime=True&amp;loc=Local"</code></p>
</li>
</ul>
<p>Let see how the code transforms here using GORM</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-comment">// Step 1: Define a struct to map to the table 'time_zone_name'</span>
<span class="hljs-keyword">type</span> TimeZoneName <span class="hljs-keyword">struct</span> {
    ID   <span class="hljs-keyword">int</span>    <span class="hljs-string">`gorm:"column:ID"`</span>   <span class="hljs-comment">// Map to column "ID"</span>
    Name <span class="hljs-keyword">string</span> <span class="hljs-string">`gorm:"column:Name"`</span> <span class="hljs-comment">// Map to column "Name"</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Step 2: Define the DSN (Data Source Name) for MySQL with parseTime=true to parse datetime properly</span>
    dsn := <span class="hljs-string">"root:password@tcp(127.0.0.1:3306)/mysql?parseTime=true"</span>

    <span class="hljs-comment">// Step 3: Use GORM to open a connection with MySQL using the DSN</span>
    db, 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, print the error and stop execution</span>
        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"failed to connect database: "</span> + err.Error())
    }

    fmt.Println(<span class="hljs-string">"Connected successfully using GORM"</span>)

    <span class="hljs-comment">// Step 4: Declare a variable to hold the query result</span>
    <span class="hljs-keyword">var</span> tz TimeZoneName

    <span class="hljs-comment">// Step 5: Use GORM's `First` function to retrieve the first record from the 'time_zone_name' table</span>
    <span class="hljs-comment">// This performs: SELECT * FROM time_zone_name LIMIT 1</span>
    result := db.Table(<span class="hljs-string">"time_zone_name"</span>).First(&amp;tz)

    <span class="hljs-comment">// Step 6: Handle query error (if no rows found or any other issue)</span>
    <span class="hljs-keyword">if</span> result.Error != <span class="hljs-literal">nil</span> {
        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"Query failed: "</span> + result.Error.Error())
    }

    <span class="hljs-comment">// Step 7: Print the result</span>
    fmt.Println(<span class="hljs-string">"Test Query Result:"</span>, tz.ID, tz.Name)
}
</code></pre>
<p>From the above code, we can see how fetching values from the timezone field transforms into clean, user-friendly Go code. GORM handles all the behind-the-scenes conversions automatically. While this might seem simple for a basic query, GORM’s benefits become even more evident as query complexity grows, making database interactions much easier and more maintainable.</p>
<p>Once you're comfortable with the code, you can <strong>run it to test the database connection</strong>.</p>
<ul>
<li><p>Create a file called main.go</p>
</li>
<li><p><strong>Type the complete code</strong> into <code>main.go</code>. Make sure all imports and logic are included as discussed earlier.</p>
</li>
<li><p><strong>Open your terminal</strong> in the project directory and run the following commands:</p>
<ul>
<li><p>Let’s download the Required package for our app using below command</p>
<ul>
<li><p><code>go get -u</code> <a target="_blank" href="http://gorm.io/gorm"><code>gorm.io/gorm</code></a></p>
</li>
<li><p><code>go get -u</code> <a target="_blank" href="http://gorm.io/driver/mysql"><code>gorm.io/driver/mysql</code></a></p>
</li>
</ul>
</li>
<li><p><code>go mod init example.com/test-db</code> - This initializes your Go module.</p>
</li>
<li><p><code>go mod tidy</code> - This downloads and adds all the required libraries and dependencies (like the MySQL driver).</p>
</li>
<li><p><code>go run main.go</code> - This compiles and runs your code.</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>This concludes our article on working with databases in Go. While there is much more to explore, I hope this overview helps you get started with building your ToDo app.</p>
<h3 id="heading-quick-summary-of-what-we-covered-so-far">Quick Summary of What We Covered So Far:</h3>
<ul>
<li><p><strong>Why a database is essential</strong> for our ToDo app project — to store, retrieve, and manage persistent data efficiently.</p>
</li>
<li><p>The <strong>various options available in Go</strong> to connect with databases, including the native <code>database/sql</code> package and third-party ORMs.</p>
</li>
<li><p>How to <strong>use Go’s native</strong> <code>database/sql</code> package to establish connections and run queries against the database.</p>
</li>
<li><p>The <strong>limitations of the native package</strong>, such as verbose boilerplate code and manual data scanning.</p>
</li>
<li><p>How <strong>Object-Relational Mapping (ORM)</strong> tools like GORM solve these limitations by automating SQL generation and data mapping.</p>
</li>
<li><p>A hands-on example showing how to <strong>use GORM to connect to a database</strong>, define models, and perform queries more conveniently.</p>
</li>
</ul>
<p>With these foundations in place, you are now equipped to build the backend for your ToDo app with confidence, leveraging Go’s ecosystem for effective database interaction.</p>
<h2 id="heading-next-step">Next Step</h2>
<p>With the foundational knowledge we've built so far, it's time to put it into practice!</p>
<p>In the upcoming sessions, we will start working on our ToDo project by:</p>
<ul>
<li><p><strong>Establishing a robust connection to the database</strong> using GORM, ensuring efficient and safe access.</p>
</li>
<li><p><strong>Configuring Go to perform CRUD operations</strong> — Create, Read, Update, and Delete — to manage our ToDo tasks seamlessly.</p>
</li>
<li><p>Implementing proper <strong>error handling, validations, and data modeling</strong> tailored to our project’s needs.</p>
</li>
</ul>
<p>This practical approach will bring our ToDo app to life, bridging the gap between theory and real-world application.</p>
]]></content:encoded></item><item><title><![CDATA[Learning By Doing Golang - ToDo App - 01]]></title><description><![CDATA[Let’s kick off our Learning by Doing with Golang series by building a simple ToDo application. This app will serve as our first hands-on project to understand the fundamentals of Go by applying them in a real-world context.
🧠 Pre-requisites
Before j...]]></description><link>https://claybrainer.com/learning-by-doing-golang-todo-app-01</link><guid isPermaLink="true">https://claybrainer.com/learning-by-doing-golang-todo-app-01</guid><category><![CDATA[learning]]></category><category><![CDATA[learning-by-doing-golang]]></category><category><![CDATA[golang]]></category><category><![CDATA[todoapp]]></category><category><![CDATA[Golang Learning]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 25 May 2025 07:58:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748159697645/3d8de030-ae50-436d-90a4-d7fef812662d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let’s kick off our <em>Learning by Doing with Golang</em> series by building a <strong>simple ToDo application</strong>. This app will serve as our first hands-on project to understand the fundamentals of Go by applying them in a real-world context.</p>
<h2 id="heading-pre-requisites">🧠 Pre-requisites</h2>
<p>Before jumping into this project, we expect you to have a <strong>basic understanding of Golang</strong>, including:</p>
<ul>
<li><p>Basic syntax and structure</p>
</li>
<li><p>Variables and data types</p>
</li>
<li><p>Arithmetic and logical operators</p>
</li>
<li><p>Conditional statements (<code>if</code>, <code>switch</code>)</p>
</li>
<li><p>Loops (<code>for</code>)</p>
</li>
<li><p>Functions and how to define/use them</p>
</li>
</ul>
<p>If you're just starting out with Go and haven't covered these fundamentals yet, we recommend learning them first. You can find great beginner-friendly <a target="_blank" href="https://go.dev/tour/welcome/1"><strong>resources here</strong></a> , and once you're comfortable, come back and join us on this hands-on journey.</p>
<blockquote>
<p><strong>Note:</strong> We’re intentionally keeping this app <strong>simple</strong> to focus on learning. As we progress, you might notice some <strong>discrepancies or missing best practices</strong>—that’s by design. We’ll note these issues along the way and revisit them after we've developed a deeper understanding of Go. For now, the goal is to get started without getting overwhelmed.</p>
</blockquote>
<h2 id="heading-what-are-we-building">📝 What Are We Building?</h2>
<p>We’re developing a <strong>basic ToDo list application</strong> that allows users to manage their tasks efficiently. The app will support the standard set of <strong>CRUD operations</strong>—<strong>Create</strong>, <strong>Read</strong>, <strong>Update</strong>, and <strong>Delete</strong> ToDos.</p>
<h3 id="heading-what-is-todo-app">What is ToDo app ?</h3>
<p>A <strong>ToDo app</strong> is a simple task management application that helps users keep track of things they need to do.</p>
<blockquote>
<p>🔗 <strong>Note:</strong> We are not building a frontend or UI for this app. All interactions will happen through <strong>RESTful APIs</strong>. This will help us focus purely on backend development using Golang, and better understand how to build, structure, and manage APIs effectively</p>
</blockquote>
<h2 id="heading-design-and-requirements">🧩 Design and Requirements</h2>
<p>Before diving into the implementation, let’s understand the <strong>design requirements</strong> of our ToDo application and the reasoning behind each decision.</p>
<h2 id="heading-what-do-we-need">🔍 What Do We Need?</h2>
<ul>
<li><p>A place to store ToDo data</p>
</li>
<li><p>A Way to Access the content Database</p>
</li>
<li><p>A Way for Users to Interact with the App</p>
</li>
</ul>
<p>Let’s see how we are planning that</p>
<h3 id="heading-1-data-storage-using-mysql">🗃️ 1. Data Storage — Using MySQL</h3>
<p>The first thing our app needs is a place to store the ToDo items. For this, we’ll use a <strong>database</strong>, as it's the most common and reliable way to store structured data in applications. Specifically, we’ll use a <strong>MySQL database</strong></p>
<h4 id="heading-why-sql">💡 Why SQL?</h4>
<p>There are two main types of databases:</p>
<ul>
<li><p><strong>SQL (Structured Query Language)</strong> – stores data in a <strong>tabular format</strong></p>
</li>
<li><p><strong>NoSQL (Not only SQL)</strong> – stores data in <strong>document or key-value format</strong></p>
</li>
</ul>
<p>We're choosing <strong>SQL (MySQL)</strong> for the following reasons:</p>
<ul>
<li><p>It’s <strong>faster and more efficient</strong> for structured data.</p>
</li>
<li><p>Our data is <strong>simple and relational</strong> (each ToDo has fields like title, description, status).</p>
</li>
<li><p>We don’t require the flexibility of a document-based (NoSQL) structure for this use case.</p>
</li>
<li><p>It fits the purpose of a basic CRUD app with limited and predictable data.</p>
</li>
</ul>
<h3 id="heading-2-database-access-using-golangs-standard-library">🔌 2. Database Access — Using Golang’s Standard Library</h3>
<p>To interact with our database and access the stored data, we need a way to <strong>connect to the database</strong> and <strong>execute SQL queries</strong>.</p>
<p>We’ll use one of <strong>Go’s standard libraries</strong> together with a <strong>MySQL driver.</strong> This setup allows us to:</p>
<ul>
<li><p>Open and manage database connections</p>
</li>
<li><p>Execute SQL commands (INSERT, SELECT, UPDATE, DELETE)</p>
</li>
<li><p>Handle errors and manage resources efficiently</p>
</li>
</ul>
<blockquote>
<p>We'll explore the exact setup and usage of these libraries in the later section.</p>
</blockquote>
<h3 id="heading-3-application-access-through-http-endpoints">🌐 3. Application Access — Through HTTP Endpoints</h3>
<p>Now, as an end-user, how do we interact with our application?</p>
<p>We’ll expose a set of <strong>HTTP endpoints</strong> that clients (like browsers, Postman, or frontend apps) can call using URLs.</p>
<p>For example:</p>
<pre><code class="lang-sql">GET    https://&lt;your-app&gt;/todos         → Get all ToDos  
POST   https://&lt;your-app&gt;/todos         → <span class="hljs-keyword">Create</span> a <span class="hljs-keyword">new</span> ToDo  
<span class="hljs-keyword">GET</span>    https://&lt;your-app&gt;/todos/{<span class="hljs-keyword">id</span>}    → <span class="hljs-keyword">Get</span> a specific ToDo  
PUT    https://&lt;your-app&gt;/todos/{<span class="hljs-keyword">id</span>}    → <span class="hljs-keyword">Update</span> a ToDo  
<span class="hljs-keyword">DELETE</span> https://&lt;your-app&gt;/todos/{<span class="hljs-keyword">id</span>}    → <span class="hljs-keyword">Delete</span> a ToDo
</code></pre>
<p>These endpoints will act as <strong>entry points</strong> to different operations within our app. Internally, each endpoint will trigger logic that interacts with the database and returns appropriate responses.</p>
<h2 id="heading-wrapping-up">🧠 Wrapping Up</h2>
<p>Now, we have a <strong>high-level understanding</strong> of what we need to build our ToDo app—and more importantly, <strong>why</strong> we need each component.</p>
<p>Don’t worry if everything doesn’t fully click yet. As we <strong>build more apps</strong> and go deeper into each concept, things will start to make sense. The key is to keep going.</p>
<h2 id="heading-whats-next">🚀 What’s Next?</h2>
<p>In the next article, we’ll focus entirely on the <strong>Database Layer</strong> of our application. Here's what we’ll cover:</p>
<ul>
<li><p>How to <strong>set up a MySQL database</strong> locally</p>
</li>
<li><p>How to <strong>connect to the database using Golang</strong></p>
</li>
<li><p>Different approaches to database interaction in Go—and <strong>why we’re choosing a specific one</strong></p>
</li>
<li><p>Step-by-step guide to <strong>implement the database layer</strong> in our ToDo app</p>
</li>
</ul>
<p>We’ll apply everything we learn directly into our project, laying a strong foundation for how our app stores and retrieves data.</p>
]]></content:encoded></item><item><title><![CDATA[Introduction - Learning by Doing Golang]]></title><description><![CDATA[What This Series Is About ?
I've been learning Golang for a while now—but despite covering the basics multiple times, I still didn't feel confident enough to build a full application. At first, I thought I just wasn’t grasping the fundamentals proper...]]></description><link>https://claybrainer.com/introduction-learning-by-doing-golang</link><guid isPermaLink="true">https://claybrainer.com/introduction-learning-by-doing-golang</guid><category><![CDATA[learning-by-doing-golang]]></category><category><![CDATA[golang]]></category><category><![CDATA[Learn Golang]]></category><category><![CDATA[learningbydoing]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 25 May 2025 05:23:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748150798330/9fd6a305-6408-487f-824e-069738ca1170.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-what-this-series-is-about">What This Series Is About ?</h1>
<p>I've been learning Golang for a while now—but despite covering the basics multiple times, I still didn't feel confident enough to build a full application. At first, I thought I just wasn’t grasping the fundamentals properly. So I went back and revised the basics again. But even that didn’t give me the confidence I was looking for.</p>
<p>That’s when I realized the issue wasn’t with what I knew, but with how I was learning.</p>
<p>What worked for me in the past with other technologies was this: learning by doing. So I decided to take that same approach with Go. Instead of only reading or watching tutorials, I’ll build real applications, face real problems, and learn the concepts as I solve them. If you’re in the same boat—feeling stuck after learning the syntax but unsure how to apply it—this series is for you. Let’s walk this path together, learn practically, and gain confidence by building.</p>
<h1 id="heading-what-to-expect">What to Expect ?</h1>
<p>In this series, I’ll be building multiple small-to-medium apps using Golang. For each project, I’ll break down:</p>
<ul>
<li><p>What components it needs and why</p>
</li>
<li><p>Why each component or library is chosen</p>
</li>
<li><p>Component syntax, pros, and limitations</p>
</li>
<li><p>How each part is implemented</p>
</li>
</ul>
<p>I will discuss a detailed walkthrough of each decision—why a certain piece of code is written the way it is, what alternatives were considered, and how everything fits together. My goal is to provide a deeper understanding of not just how to build with Go, but why certain practices or patterns are followed. So when you encounter similar problems in the future, you’ll have the insight to adapt and solve them.</p>
<h1 id="heading-how-to-follow-the-series">How to Follow the Series ?</h1>
<p>Each topic/project in this series will be divided into three parts:</p>
<ol>
<li><h2 id="heading-understanding-the-layer-well-start-by-identifying-the-different-layers-in-the-applike-data-service-handler-etcand-answer">Understanding the Layer We’ll start by identifying the different layers in the app—like data, service, handler, etc.—and answer:</h2>
</li>
</ol>
<ul>
<li><p>What is the purpose of this layer?</p>
</li>
<li><p>What role does it play in the app?</p>
</li>
<li><p>Why do we need it?</p>
</li>
</ul>
<ol start="2">
<li><h2 id="heading-learning-the-concepts-next-well-focus-on-the-specific-components-or-modules-in-go">Learning the Concepts Next, we’ll focus on the specific components or modules in Go:</h2>
</li>
</ol>
<ul>
<li><p>How do we implement them in Go?</p>
</li>
<li><p>What tools or libraries are available?</p>
</li>
<li><p>Why did we choose a specific tool or method?</p>
</li>
</ul>
<ol start="3">
<li><h2 id="heading-putting-it-all-together-finally-well-implement-the-component-inside-our-project">Putting It All Together Finally, we’ll implement the component inside our project:</h2>
</li>
</ol>
<ul>
<li><p>Connecting the dots from concept to code</p>
</li>
<li><p>Solving real problems using the ideas we’ve discussed</p>
</li>
<li><p>Making sure we understand how and why it works</p>
</li>
</ul>
<p>If you’re someone who learns better by building, not just reading, you’ll feel right at home. Let’s code, break things, fix them, and grow—together.</p>
]]></content:encoded></item><item><title><![CDATA[Designing Reliable SonarQube Infrastructure: Key Factors to Consider]]></title><description><![CDATA[Hello, DevOps Enthusiasts! Welcome back! In our last blog, we explored what SonarQube is, why it’s essential, and the capabilities it offers. If you missed it, feel free to catch up here.
This time, we’re diving into the practicalities of setting up ...]]></description><link>https://claybrainer.com/designing-reliable-sonarqube-infrastructure-key-factors-to-consider</link><guid isPermaLink="true">https://claybrainer.com/designing-reliable-sonarqube-infrastructure-key-factors-to-consider</guid><category><![CDATA[SonarQube Setup]]></category><category><![CDATA[SonarQube Infrastructure]]></category><category><![CDATA[SonarQube Architecture]]></category><category><![CDATA[SonarQube Best Practices]]></category><category><![CDATA[SonarQube Deployment]]></category><category><![CDATA[SonarQube Cloud]]></category><category><![CDATA[Scalable Infrastructure]]></category><category><![CDATA[sonarqube]]></category><category><![CDATA[Code Quality]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[static code analysis]]></category><category><![CDATA[DevOps tools]]></category><category><![CDATA[software Quality Assurance]]></category><category><![CDATA[DevOps practices]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 03 Nov 2024 06:48:27 GMT</pubDate><content:encoded><![CDATA[<p>Hello, DevOps Enthusiasts! Welcome back! In our last blog, we explored <em>what SonarQube is</em>, <em>why it’s essential</em>, and <em>the capabilities it offers</em>. If you missed it, feel free to catch up <a target="_blank" href="https://claybrainer.com/part-1-sonarqube-essentials-why-its-crucial-in-ci-pipelines">here</a>.</p>
<p>This time, we’re diving into the practicalities of setting up a SonarQube infrastructure. We’ll cover the critical considerations for designing a scalable and effective SonarQube setup, along with the various deployment options available to help you choose the best fit for your needs.</p>
<p>Here's what we’ll discuss:</p>
<ul>
<li><p><strong>Key Considerations for Setting Up SonarQube Infrastructure</strong> – Factors like server requirements, high availability, security, and scaling for large codebases.</p>
</li>
<li><p><strong>SonarQube Setup Options</strong> – Different methods to deploy SonarQube, whether self-hosted or cloud-based, and how to select the right one for your environment.</p>
</li>
</ul>
<p><strong>Imagine this:</strong> <em>Your team just got the green light from leadership to set up a SonarQube infrastructure. It’s a big responsibility, and you’re excited—but also, where do you even begin?</em></p>
<p>Let’s get started!</p>
<h2 id="heading-sonarqube-architecture">SonarQube Architecture:</h2>
<p><em>OMG!!! Why bother with the architecture of SonarQube when we just want to set it up, right?</em></p>
<p><em>Great Question!</em></p>
<p>Well, here's the twist: Understanding SonarQube’s components is crucial for making informed infrastructure decisions.</p>
<p><em>Imagine this:</em> Your business is just kicking off, so you only need a small setup. But in a couple of months, as your team starts coding up a storm, you’ll have a mountain of code waiting to be scanned. Now, without understanding how SonarQube is built, how would you know what kind of infrastructure can handle that future load?</p>
<p>So, before diving into setup, let’s get a quick overview of SonarQube’s design. I promise it won’t take long – and trust me, this knowledge will prove invaluable when troubleshooting in the future!</p>
<p>Hope I’ve convinced you to stick around for this section 😊</p>
<p>SonarQube’s architecture is designed with three main layers: the <strong>Compute Layer</strong>, the <strong>Search Layer</strong>, and the <strong>Database Layer</strong>. Here’s a quick rundown of each:</p>
<ul>
<li><p><strong>Compute Layer</strong> : The Processing Powerhouse. Where Analysis Magic Happens</p>
</li>
<li><p><strong>Search Layer:</strong> The Lightning-Fast Lookup. Your Code’s Search Engine</p>
</li>
<li><p><strong>Database Layer:</strong> This is where all SonarQube data stored. The Memory Bank for Your Project</p>
</li>
</ul>
<p>If you want to learn about each layer in detail go through the below section else feel free to skip to next topic <strong>"Factors to Consider When Designing SonarQube Infrastructure"</strong></p>
<h3 id="heading-sonarqube-design-layer-in-detail">SonarQube Design Layer in Detail</h3>
<ul>
<li><p><strong>Compute Layer:</strong></p>
<ul>
<li><p><strong>What it is:</strong> This layer is the brain of SonarQube, where most of the analysis and processing happens.</p>
</li>
<li><p><strong>What It Does</strong> <strong>:</strong> It receives code analysis reports from various sources, such as CICD pipeline through SonarScanner, and then processes and interprets this data to detect issues, measure code quality, and provides actionable feedback. Think of it as the heavy lifter that turns raw code data into insightful metrics.</p>
</li>
</ul>
</li>
<li><p><strong>Search Layer:</strong></p>
<ul>
<li><p><strong>What it is:</strong> This is the search and indexing engine, usually powered by Elasticsearch.</p>
</li>
<li><p><strong>What It Does</strong>: The Search Layer indexes all the code analysis data, making it fast and easy to search and retrieve results.</p>
</li>
<li><p><em>If you’ve ever struggled to understand the role of the Search Layer, just know that I did too! Let’s break it down with a simple, relatable example:</em></p>
<ul>
<li><p><strong>Example</strong> :</p>
<ul>
<li><p>Imagine you have a massive codebase with hundreds of files. One day, you need to find instances of a particular security vulnerability, like the use of <code>password</code> (keyword) in JavaScript files (which can introduce security risks). Without the Search Layer, you’d have to go through each file one by one, which would take a considerable amount of time.</p>
</li>
<li><p>But thanks to the Search Layer, SonarQube has already indexed your entire codebase. So, you just type "<code>password"</code> into SonarQube's search, and it instantly pulls up every location where that keyword appears. The Search Layer’s indexing allows for lightning-fast lookups, even in enormous codebases, making it easy to spot and tackle issues quickly!</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Database Layer</strong></p>
<ul>
<li><p><strong>What it is:</strong> This is the storage layer where SonarQube keeps all its data.</p>
</li>
<li><p><strong>What It Does</strong> <strong>:</strong> The Database Layer stores project histories, analysis reports, configurations, and user data. SonarQube relies on this layer to maintain a reliable record of code quality over time</p>
</li>
</ul>
</li>
</ul>
<p>Hope this gives you a clearer understanding of each layer! We’ll dive into how these components are crucial for designing the infrastructure in the later section.</p>
<h2 id="heading-factors-to-consider-when-designing-sonarqube-infrastructure">Factors to Consider When Designing SonarQube Infrastructure</h2>
<p>Let’s see what are the factors to be considered for designing the SonarQube infra</p>
<h3 id="heading-lines-of-code-loc">Lines of Code [LOC]</h3>
<ul>
<li><p><strong>What Does It Mean :</strong> When we talk about <strong>Lines of Code</strong> (LOC) in the context of SonarQube, we're diving into how many lines of code we expect our SonarQube server to scan. This figure is crucial for planning your infrastructure.</p>
</li>
<li><p>Now, you might be wondering, “<em>Wait, seriously!!! Are you asking me to count the lines of code for each project?</em>” Not exactly! Think of it as an estimated figure—a ballpark number to help gauge your infrastructure needs</p>
</li>
<li><p>For a small business, a good starting point is around <strong>100,000 lines of code</strong>. This is a safe base value that accounts for growth and varying project sizes. It’s like laying a solid foundation before building your dream house!</p>
</li>
</ul>
<h3 id="heading-expected-number-of-concurrent-user-sessions">Expected Number of Concurrent User Sessions</h3>
<ul>
<li><p><strong>What Does It Mean :</strong> This refers to the anticipated number of users who will be <strong>actively engaged</strong> in SonarQube tasks on the SonarQube system at the same time, such as searching the codebase, reviewing metrics, and performing other relevant activities.</p>
</li>
<li><p>For a small business it can be considered as 5-10 active users</p>
</li>
</ul>
<h3 id="heading-frequency-of-scans-expected-per-hour"><strong>Frequency of Scans Expected per Hour</strong>:</h3>
<ul>
<li><p><strong>What Does It Mean :</strong> This refers to how many times you anticipate running scans on your projects within an hour.</p>
</li>
<li><p>This includes both frequent scans of a single project and simultaneous scans of multiple projects. The key factor is the number of scans performed per hour.</p>
</li>
<li><p>Understanding the frequency of scans helps determine the necessary computational resources and the potential load on the SonarQube server.</p>
</li>
</ul>
<h3 id="heading-project-count"><strong>Project Count</strong>:</h3>
<ul>
<li><p><strong>What Does It Mean :</strong> This refers to the total number of projects that will be analyzed by SonarQube.</p>
</li>
<li><p>The more projects you have, the greater the resource demands on your infrastructure.</p>
</li>
</ul>
<p>These are some fundamental and key factors to consider when planning your SonarQube infrastructure.</p>
<h2 id="heading-sonarqube-infrastructure-planning">SonarQube Infrastructure Planning</h2>
<p>When it comes to setting up your SonarQube infrastructure, you have two main roads to choose from:</p>
<h3 id="heading-single-instance-approach-all-aboard">Single Instance Approach: All Aboard! 🚀</h3>
<ul>
<li><p><strong>What does it mean ?</strong></p>
<ul>
<li><p>Imagine this: all three layers of your SonarQube infrastructure—Compute, Search, and the Database—are packed neatly into one cozy machine. That’s what we call the <strong>Single Instance Approach</strong>!</p>
</li>
<li><p><em>But hold on</em>—<em>there’s a twist!</em> Sometimes, you might decide to keep the Compute and Search layers together on one machine while giving the Database its own little vacation on a separate server. <em>Surprise!</em> That still counts as a single instance!</p>
</li>
</ul>
</li>
<li><p><strong>What Are the Pros? 🌟</strong></p>
<ul>
<li><p><strong>Simplicity is Key:</strong> Just one instance to maintain! This means more time for you to focus on other important tasks.</p>
</li>
<li><p><strong>Cost-Effective:</strong> With only one instance to manage, you’ll save on both running and management costs.</p>
</li>
</ul>
</li>
<li><p><strong>What Are the Cons? ⚠️</strong></p>
<ul>
<li><p><strong>Single Point of Failure:</strong> If something goes wrong with that one instance, it could throw a wrench in your plans. Yikes!</p>
</li>
<li><p><strong>Not Ideal for Larger Projects:</strong> As your projects grow, this approach might feel a bit cramped.</p>
</li>
<li><p><strong>Resource Limitations:</strong> You’ll likely need to pump up the instance’s resources as your project expands, which can lead to increased costs.</p>
</li>
<li><p><strong>Potential Latency:</strong> Since everything is managed by a single instance, you may experience some sluggishness as demands increase.</p>
</li>
</ul>
</li>
<li><p><strong>When to Consider This Approach?</strong></p>
<p>  Let’s paint a picture: your business is small, and your SonarQube usage is light. Here are some numbers to consider:</p>
<ul>
<li><p><strong>Lines of Code (LoC):</strong> Keep it under 500,000 lines of code.</p>
</li>
<li><p><strong>Concurrent Users:</strong> Aim for 10-20 active users at a time.</p>
</li>
<li><p><strong>Frequency of Scans:</strong> Limit it to less than 5 scans per hour.</p>
</li>
</ul>
</li>
</ul>
<p>    If this sounds like your scenario, then the Single Instance Approach might just be your ideal fit!</p>
<p><strong>Note:</strong> <strong>It is always a best practice to separate the Database from the SonarQube server when deploying for production use to enhance performance, security, and scalability.</strong></p>
<h3 id="heading-clustered-approach-spread-the-love">Clustered Approach: Spread the Love! 🌐</h3>
<ul>
<li><p><strong>What does it mean ?</strong></p>
<ul>
<li><p>Here, each layer of your SonarQube infrastructure—Compute, Search, and Database—receives dedicated resources.</p>
</li>
<li><p>In this setup, you can host each layer on separate instances or even in a containerized environment such as Docker swarm or Kubernetes. This separation of concerns not only streamlines operations but also optimizes resource usage, letting each layer shine in its own right</p>
</li>
</ul>
</li>
<li><p><strong>What Are the Pros? 🌟</strong></p>
<ul>
<li><p><strong>Enhanced Performance:</strong> With dedicated resources for each layer, you can expect better performance.</p>
</li>
<li><p><strong>Scalability at Its Best:</strong> As your project grows, you can scale individual components independently. Need more power for the Compute layer? Easy peasy! Just add resources without impacting the other layers.</p>
</li>
<li><p><strong>Increased Reliability:</strong> If one instance encounters issues, the others can continue to operate. It’s like having a backup team ready to step in!</p>
</li>
<li><p><strong>Improved User Experience:</strong> With better resource allocation and no single point of failure, your users will enjoy smoother and faster interactions with SonarQube.</p>
</li>
</ul>
</li>
<li><p><strong>What Are the Cons? ⚠️</strong></p>
<ul>
<li><p><strong>Complex Management:</strong> Managing multiple instances can be more complicated. You’ll need to ensure that all components communicate well and are configured correctly</p>
</li>
<li><p><strong>Higher Initial Costs:</strong> Setting up a clustered environment often requires a higher initial investment in infrastructure.</p>
</li>
<li><p><strong>Networking Overhead:</strong> Communication between different instances can introduce some latency. You’ll want to keep an eye on your network configuration to minimize any slowdowns.</p>
</li>
</ul>
</li>
<li><p><strong>When to Consider This Approach?</strong></p>
<ul>
<li><p><strong>Lines of Code (LoC):</strong> If you’re dealing with over 500,000 lines of code, it’s time to think about spreading the love!</p>
</li>
<li><p><strong>Concurrent Users:</strong> If you anticipate more than 20 active users at a time, a clustered setup will help manage the load efficiently.</p>
</li>
<li><p><strong>Frequency of Scans:</strong> If you need to run more than 5 scans per hour, a clustered environment will provide the necessary resources to handle the demand smoothly.</p>
</li>
</ul>
</li>
</ul>
<p>With this I hope you got better understanding on how to plan the infrastructure for SonarQube. In the next section lets discuss about the resource planning.</p>
<h2 id="heading-hardware-requirement-for-sonarqube-infra">Hardware Requirement for SonarQube Infra</h2>
<p>One of the next questions you may have when setting up SonarQube infrastructure is how much resource to allocate.</p>
<p>Let me give you the short evaluation on this. FYKI this is just an assumption you may tweek the value as per your need. But this can be considered as the starting point.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Business Size</strong></td><td><strong>Description</strong></td><td><strong>CPU</strong></td><td><strong>Memory</strong></td><td><strong>Disk</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Small Business</td><td>Less than 100,000 Lines of code, and 5-10 users</td><td>2</td><td>2GB</td><td>High I/O Ops preferrably SSD</td></tr>
<tr>
<td>Medium Business</td><td>Between 100,000 - 1,000,000 Lines of Code and 10-25 users</td><td>4 - 8</td><td>16GB Ram</td><td>SSD / HDD with 15000 RPM with I/O Ops</td></tr>
<tr>
<td>Large Business</td><td>Above 1,000,000 Lines of code and above 25 users. Clustered approach is preferable</td><td>8- 16</td><td>32</td><td>SSD / HDD with 15000 RPM with I/O Ops.</td></tr>
</tbody>
</table>
</div><p><em>Note: For Large Business Clustered Approach is preferable as it will be easy to scale and resources can be allocated on Needed layer</em></p>
<h3 id="heading-strategy-to-scale"><strong>Strategy to Scale:</strong></h3>
<p><strong><em>Note: There is no one-size-fits-all solution; you will need to adjust these guidelines to find the optimal and cost-effective setup for your infrastructure.</em></strong></p>
<p><strong>CPU</strong>:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Parameter</td><td>Description</td><td>Base</td><td>Scale</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td>Lines of Code</td><td>Every 200,000 added beyond 1,000,000</td><td>8</td><td>Add 1 CPU</td><td>If your code is 1,200,000 the CPU is 9 or 10</td></tr>
<tr>
<td>Project Count</td><td>Every 25 Projects beyond 50 add 1 CPU</td><td>8 CPU for first 50 projects</td><td>Add 1 CPU for 25 projects</td><td>-</td></tr>
<tr>
<td>Concurrent Scan</td><td>Every 10 Scan beyond 20 Projects</td><td>8 CPU for 20 Concurrent scans</td><td>Add 1 CPU for every 10 Concurrent Scan</td><td>-</td></tr>
<tr>
<td>No of Users</td><td>Every 10 users beyond 25 Concurrent Active User</td><td>8 CPU for 50 Concurrent Active User</td><td>Add 1 CPU for ever 10 Users</td><td>-</td></tr>
</tbody>
</table>
</div><ul>
<li><p>For example lets say that your Loc is 1,200,000 and your project count is around 65 then the CPU calculation is</p>
<ul>
<li><p>8 Base CPU</p>
</li>
<li><p>1 For 200 LOC</p>
</li>
<li><p>1 for 65 Project Count</p>
</li>
</ul>
</li>
<li><p>So the CPU configuration for Compute Layer is 10 CPUs</p>
</li>
</ul>
<p><strong>Memory (RAM):</strong></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Parameter</td><td>Description</td><td>Base</td><td>Scale</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td>Lines of Code</td><td>Every 200,000 lines beyond 1,000,000 LOC</td><td>16 GB for up to 1,000,000 LOC</td><td>Add 2 GB for every additional 200,000 LOC</td><td>If your codebase is 1,200,000 LOC, total memory is 18 GB</td></tr>
<tr>
<td>Project Count</td><td>Every 25 projects beyond 50</td><td>16 GB for the first 50 projects</td><td>Add 1 GB for every additional 25 projects</td><td>-</td></tr>
<tr>
<td>Concurrent Scans</td><td>Every 10 concurrent scans beyond 20</td><td>16 GB for 20 concurrent scans</td><td>Add 2 GB for every additional 10 concurrent scans</td><td>-</td></tr>
<tr>
<td>Number of Users</td><td>Every 10 concurrent active users beyond 25</td><td>16 GB for 25 active users</td><td>Add 1 GB for every additional 10 users</td><td>-</td></tr>
</tbody>
</table>
</div><ul>
<li><p>For example lets say that your LoC is 1,200,000 and your project count is around 65 then the Memory calculation is</p>
<ul>
<li><p>16 GB Ram</p>
</li>
<li><p>2GB for 200 LOC</p>
</li>
<li><p>1 GB for 65 Project Count</p>
</li>
</ul>
</li>
<li><p>So the Memory configuration for Compute Layer is around 20 GB of Ram as 19 is not an option.</p>
</li>
</ul>
<p><strong><em>Note: For a clustered setup, the above specifications apply to the Compute layer. To see recommended resource allocation for the Search layer, check out this</em></strong> <a target="_blank" href="https://docs.sonarsource.com/sonarqube/9.9/requirements/hardware-recommendations/#elasticsearch"><strong><em>link</em></strong></a></p>
<h2 id="heading-choosing-the-right-sonarqube-edition-for-your-business"><strong>Choosing the Right SonarQube Edition for Your Business</strong></h2>
<p>When it comes to selecting the best SonarQube Edition for your needs, it all boils down to your project scale, security requirements, and specific features. SonarQube offers a range of editions, each tailored to different business requirements—from community options to enterprise-level offerings with advanced features.</p>
<p>Rather than diving into all the specifics here, check out the SonarQube website for a detailed breakdown of each edition and find the one that’s right for you! <a target="_blank" href="https://www.sonarsource.com/plans-and-pricing/">Learn more about SonarQube editions here</a>.</p>
<h2 id="heading-cloud-variant-the-hassle-free-path"><strong>Cloud Variant: The Hassle-Free Path!</strong></h2>
<p>Hey there! Don’t feel like wrestling with infrastructure details? Wish you could just jump straight into scanning code without the setup headache? Good news—you can! With SonarQube’s Cloud version, all you need to do is subscribe, upload your code, and let SonarQube handle the rest.</p>
<p>No infrastructure setup, no resource juggling—it’s all handled by SonarQube! You’ll only pay based on the number of Lines of Code to scan and the number of users. <a target="_blank" href="https://www.sonarsource.com/plans-and-pricing/sonarcloud/">Check out the full pricing breakdown for the Cloud Variant and let SonarQube take care of the heavy lifting</a>!</p>
<h2 id="heading-which-sonarqube-edition-should-you-choose"><strong>Which SonarQube Edition Should You Choose?</strong></h2>
<p>Great question! Whether you go for an on-premises setup or the cloud variant depends on your business needs and priorities.</p>
<p>If you’re looking for full control over your infrastructure, with the flexibility to scale and configure resources as your team grows, an on-premises edition might be the right fit. Just remember, you’ll want to factor in maintenance and resource planning to keep things running smoothly.</p>
<p>On the other hand, if you want a streamlined, maintenance-free experience, SonarQube’s Cloud variant could be the ideal choice. You get all the core functionality of SonarQube, minus the infrastructure management—perfect for teams focused on fast deployment and easy scaling.</p>
<h2 id="heading-summary">Summary</h2>
<ul>
<li><p><strong>SonarQube Architecture</strong>: Understanding the components of SonarQube's architecture—Compute, Search, and Database Layers—is crucial for making informed infrastructure decisions.</p>
</li>
<li><p><strong>Compute Layer</strong>: This layer processes code analysis reports, turning raw data into actionable metrics regarding code quality.</p>
</li>
<li><p><strong>Search Layer</strong>: The Search Layer indexes analysis data for rapid retrieval, enabling quick identification of code issues across large codebases.</p>
</li>
<li><p><strong>Database Layer</strong>: This storage layer maintains project histories, analysis reports, and configurations, ensuring reliable tracking of code quality.</p>
</li>
<li><p><strong>Factors to Consider</strong>: Key considerations for designing SonarQube infrastructure include Lines of Code (LOC), expected user sessions, scan frequency, and project count.</p>
</li>
<li><p><strong>Single Instance Approach</strong>: This cost-effective method consolidates all SonarQube layers on a single machine, ideal for small businesses with minimal resource demands.</p>
</li>
<li><p><strong>Clustered Approach</strong>: By separating layers into individual instances, this approach enhances performance, scalability, and reliability, suitable for larger projects.</p>
</li>
<li><p><strong>Hardware Requirement</strong>: Resource allocation guidelines suggest starting points based on business size and expected workload to ensure optimal performance.</p>
</li>
<li><p><strong>Choosing the Right SonarQube Edition</strong>: Selecting the appropriate SonarQube edition depends on project scale and feature requirements, with options ranging from community to enterprise levels.</p>
</li>
<li><p><strong>Cloud Variant</strong>: The Cloud variant offers a hassle-free solution for users who prefer not to manage infrastructure, allowing immediate access to SonarQube functionalities.</p>
</li>
</ul>
<p>Thank you for following along with this blog! I hope it has provided you with valuable insights into designing SonarQube infrastructure. In the next installment, we will set up SonarQube and explore how to scan a project and analyze the resulting reports.</p>
<p>Until then, see you all, and happy learning!</p>
]]></content:encoded></item><item><title><![CDATA[SonarQube Essentials: Why It’s Crucial in CI Pipelines]]></title><description><![CDATA[Hello and welcome, fellow DevOps enthusiasts! Thanks for joining this exploration. In the upcoming sections, we’ll dive into SonarQube, uncover its benefits, and discuss why it’s essential for your projects.
Let’s jump in and get the code quality par...]]></description><link>https://claybrainer.com/part-1-sonarqube-essentials-why-its-crucial-in-ci-pipelines</link><guid isPermaLink="true">https://claybrainer.com/part-1-sonarqube-essentials-why-its-crucial-in-ci-pipelines</guid><category><![CDATA[Static Application Security Testing (SAST]]></category><category><![CDATA[Quality Gates]]></category><category><![CDATA[sonarqube]]></category><category><![CDATA[Code Quality]]></category><category><![CDATA[DevOps tools]]></category><category><![CDATA[static code analysis]]></category><category><![CDATA[code analysis]]></category><category><![CDATA[Security vulnerabilities ]]></category><category><![CDATA[technical-debt]]></category><category><![CDATA[software development]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[Continuous Integration tools]]></category><category><![CDATA[code coverage]]></category><category><![CDATA[maintainability]]></category><category><![CDATA[sonarqube quality gate]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 20 Oct 2024 13:28:41 GMT</pubDate><content:encoded><![CDATA[<p><strong>Hello and welcome,</strong> fellow DevOps enthusiasts! Thanks for joining this exploration. In the upcoming sections, we’ll dive into SonarQube, uncover its benefits, and discuss why it’s essential for your projects.</p>
<p>Let’s jump in and get the code quality party started!</p>
<p>Let’s start with this question:</p>
<h2 id="heading-what-is-sonarqube">What is SonarQube</h2>
<ol>
<li>SonarQube is a <strong>code quality and security analysis tool</strong> that also functions as a <strong>Static Application Security Testing</strong> (SAST) tool.</li>
</ol>
<ul>
<li><p><strong>Wait! What is SAST (Static Application Security Testing) ?</strong></p>
<p>  SAST is a testing technique that analyses an application’s source code to identify security vulnerabilities without executing it.</p>
</li>
<li><p>For example Imagine having someone who examines every line of code, highlighting potential security weaknesses(such as Sensitive Data Exposure, <a target="_blank" href="https://en.wikipedia.org/wiki/SQL_injection">SQL Injection</a>) before you package and run the application.</p>
</li>
</ul>
<h2 id="heading-why-and-when-should-i-use-sonarqube">Why and When Should I use SonarQube ?</h2>
<p>Great question! Here’s a quick answer:</p>
<ul>
<li><p><strong>To avoid Technical debt</strong> in your codebase by catching code quality issues early.</p>
</li>
<li><p><strong>To protect your code from security vulnerabilities,</strong> ensuring it meets security standards. (Eg: Sensitive Data Exposure, Hardcoded Secrets, <a target="_blank" href="https://en.wikipedia.org/wiki/SQL_injection">SQL Injection</a>)</p>
</li>
<li><p><strong>When you lack the time or resources</strong> to manually review every line of code for compliance with best practices.</p>
</li>
<li><p><strong>To ensure sufficient test coverage,</strong> helping you confirm that your code behaves as expected and meets its requirements.</p>
</li>
<li><p><strong>To maintain coding standards consistently</strong></p>
</li>
<li><p><strong>To continuously improve code quality over time</strong></p>
</li>
</ul>
<p><strong>If you want to tackle any or all of the issues mentioned above, integrating SonarQube into your development stack can significantly enhance your code quality and security practices, ensuring a more robust and maintainable codebase.</strong></p>
<p><strong>Technical debt:</strong> Some of you might be wondering, "What is technical debt in a codebase?" Trust me, I had the same question at one point! 😊</p>
<ul>
<li><p>It refers to the future cost of rework or maintenance required when code is written quickly to meet immediate needs rather than following the best practices or optimal solutions. (Some Common Technical Debts refers to coding practices, duplicated code, and complex methods). » Wait ! what 😲 ? Can you speak in a language what is understandable</p>
</li>
<li><p>Got you ! Let’s consider Flipkart preparing for the Big Billion Day sale. They want to apply a 30% discount on all products. To meet this urgent request, instead of implementing a flexible discount logic that allows for easy changes, you hardcode the 30% discount directly into the codebase.</p>
</li>
<li><p>Later, if they decide to change the discount to 20% <strong>for specific products,</strong> you’ll need to go back and manually update the code. This creates technical debt because, at some point, you’ll have to rewrite the entire discount logic to make it adaptable for various scenarios. If you keep taking such quick shortcuts to meet urgent needs, the codebase will eventually become messy and difficult to maintain.</p>
</li>
</ul>
<p>I hope it's now clear why and when to use SonarQube. Let's explore what SonarQube offers to address all these issues.</p>
<h2 id="heading-what-capabilities-does-sonarqube-offer-to-tackle-these-challenges">What Capabilities Does SonarQube Offer to Tackle These Challenges?</h2>
<p>Let us explore the various capabilities offered by SonarQube to address these challenges and examine how we can effectively leverage them.</p>
<ol>
<li><h3 id="heading-code-quality-management"><strong>Code Quality Management</strong></h3>
</li>
</ol>
<ul>
<li><p><strong>Code Smells Detection:</strong></p>
<ul>
<li><p>As the name implies, the <strong>code smell report highlights potential areas of poor design or bad coding practices</strong> that could lead to maintainability challenges down the road.</p>
</li>
<li><p>It offers developers guidance on coding standards and best practices, along with suggestions for improvement.</p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check this on the SonarQube Dashboard page of the project itself.</em></p>
</li>
<li><p><strong>Note:</strong> <em>While the suggestions provided by the code smell report aren't necessarily bugs, they serve as valuable indicators that the detected code may lead to potential issues in the future. Let's embrace these insights and enhance our coding practices together!</em></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729421212472/3de5aec7-bfac-40fc-a8e9-5b1f0e9588ad.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Maintainability Rating</strong></p>
<ul>
<li><p>SonarQube assigns a rating to your code known as the <strong>Maintainability Rating</strong>, which is based on the <strong>Technical Debt Ratio</strong>. This rating reflects the effort required to resolve identified issues.</p>
</li>
<li><p>By leveraging this rating, teams can effectively prioritise their refactoring efforts, concentrating on areas with lower maintainability scores to enhance overall code quality.</p>
</li>
<li><p>You can view the available Maintainability Ratings through <a target="_blank" href="https://docs.sonarsource.com/sonarcloud/digging-deeper/metric-definitions/#maintainability">this link.</a></p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check the Maintainability Rating Under measures tab in SonarQube</em></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729421978788/53ab42ec-23a5-4e9e-9165-c13dc2d1364e.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Duplicated Code Detection</strong></p>
<ul>
<li><p>Identifies the duplicate blocks of codes.</p>
</li>
<li><p>The lesser the number the more better your code is</p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check this on the SonarQube Dashboard page of the project itself.</em></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729425497209/83cdef85-1190-4424-9588-9c503865ce46.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
<li><p><strong>Technical Debt Management:</strong></p>
<ul>
<li><p>SonarQube continuously tracks the evolution of technical debt, making it easier for teams to prioritise addressing technical issues.</p>
</li>
<li><p>Offers historical trends and dashboards to visualize how technical debt changes over time.</p>
</li>
<li><p>It also provides you the effort required to fix the code issue in terms of time such as Hours or days.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729427458451/12dec7be-f6c3-4d71-9129-4e6eef12c08e.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<ol start="2">
<li><h3 id="heading-security-vulnerability-detection"><strong>Security Vulnerability Detection</strong></h3>
</li>
</ol>
<ul>
<li><p><strong>Static Application Security Testing (SAST):</strong></p>
<ul>
<li><p>SonarQube performs SAST to identify common security vulnerabilities, such as SQL injection, cross-site scripting (XSS), and hardcoded passwords on the source code.</p>
</li>
<li><p>Provides remediation guidance for detected security issues, helping developers fix vulnerabilities before code reaches production.</p>
</li>
</ul>
</li>
<li><p><strong>Security Hotspots</strong></p>
<ul>
<li><p>Highlights areas of code that require manual review for potential security implications.</p>
</li>
<li><p>SonarQube comes with <strong>built-in security rules</strong> based on industry standards and best practices. These rules help developers adhere to security guidelines by flagging potential vulnerabilities during the development process.</p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check this on the SonarQube</em> <strong><em>Security Hotspot Tab</em></strong></p>
</li>
</ul>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729425944009/c5b99e6c-32ff-4662-b658-4ca6f811d033.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ol start="3">
<li><h3 id="heading-code-coverage-and-test-execution">Code Coverage and Test Execution</h3>
</li>
</ol>
<ul>
<li><p><strong>Code Coverage Analysis:</strong></p>
<ul>
<li><p>This feature shows the percentage of your code that is covered by test cases.</p>
</li>
<li><p><strong>Why it is needed?</strong> Test cases are written by developers to check if the code works as it should. They help ensure that the program does what it's supposed to do.</p>
</li>
<li><p>Code Coverage Analysis Identifies untested parts of the codebase, allowing teams to prioritise adding tests to improve code reliability.</p>
</li>
<li><p><strong><em>Note:</em></strong> <em>You need to perform code coverage analysis using the appropriate tool for your programming language and then export the report to SonarQube for it to be available in the SonarQube dashboard.</em></p>
</li>
<li><p><strong>Analysis</strong>: The more the percentage the better the code is</p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check this on the SonarQube Dashboard page of the project itself.</em></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729426362703/4bec9b8c-b1bd-40be-9c2a-7be2c7c62b49.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<ol start="4">
<li><h3 id="heading-quality-gate"><strong>Quality Gate</strong></h3>
</li>
</ol>
<ul>
<li><p>As the name suggests, it acts as a gate between your code and the production branch.</p>
</li>
<li><p>It allows you to define quality gates (rules) based on metrics such as code coverage, code smells, bugs, and security vulnerabilities. For example, you can set rules like a minimum code coverage of 90% or a maximum duplication rate of 10% etc.</p>
</li>
<li><p>This ensures that only code that meets your quality standards is merged, helping maintain the integrity and reliability of the main branch.</p>
</li>
<li><p><strong><em>Where Can I check this on Sonarqube?</em></strong> <em>You can check this on the SonarQube</em> <strong><em>Quality Gate Section</em></strong></p>
</li>
<li><p><img src="https://dx.appirio.com/quality-sonarqube/granting-permissions/SonarQube-QualityGate-2.png" alt /></p>
</li>
</ul>
<ol start="5">
<li><h3 id="heading-quality-profile"><strong>Quality Profile</strong></h3>
</li>
</ol>
<ul>
<li><p>A Quality Profile is a collection of coding rules and guidelines that define what constitutes acceptable code quality for a specific programming language or project. It dictates how code is analysed and what issues are reported.</p>
</li>
<li><p>Quality Profiles help teams establish consistent coding standards across projects, allowing for tailored rule sets based on language and project needs.</p>
</li>
<li><p>In the screenshot below, you can see the quality profiles for Java and Go. You can set quality profiles, which are sets of rules tailored for specific programming languages, to ensure that your code meets the desired quality standards. Each profile contains predefined rules that help identify issues related to code quality, security vulnerabilities, and best practices specific to that language.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729427165884/896e816a-9a95-49d8-9ac8-07b16cc09979.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<p>Alright I understood the tools sets that SonarQube offers. Now my next question will be How can I use this tool what are the options I have</p>
<h2 id="heading-how-to-use-sonarqube-to-scan-code">How to use SonarQube to Scan Code</h2>
<ul>
<li><p>Before we dive into how to use SonarQube, let me share a little secret with you: <strong>SonarQube is essentially a reporting and analysis tool that presents the analysis of your code in a structured way</strong>. So you need to generate report and send it to SonarQube to make full use of it.</p>
</li>
<li><p><strong>How can I Generate Report ?</strong></p>
<ul>
<li><p>Good Question! There is a tool called <strong>SonarScanner.</strong> This will scan your code and send the report to the SonarQube server.</p>
</li>
<li><p>Once this is done, you can fully harness the powerful features that SonarQube has to offer!</p>
</li>
</ul>
</li>
<li><p>Now you may have many questions like,</p>
<ul>
<li><p>How SonarScanner knows my SonarQube server</p>
</li>
<li><p>How it knows what to scan and so on.</p>
</li>
<li><p>We will cover all those things in the series of blogs on “How to scan our source code using SonarQube“</p>
</li>
<li><p>For now, please remember that you need SonarScanner to generate reports and send them to SonarQube, where the magic of code analysis happens.</p>
</li>
</ul>
</li>
<li><p>As we know, we need to use SonarScanner to generate reports. Here are the available options for running SonarScanner on your code:</p>
<ul>
<li><p><strong>Run Locally</strong>: You can execute SonarScanner on your local machine to analyze your code and generate reports.</p>
</li>
<li><p><strong>Configure in Your CI Pipeline</strong>: You can integrate SonarScanner into your Continuous Integration (CI) pipeline, allowing it to run automatically each time you commit code. This ensures that code quality checks are consistently applied.</p>
</li>
<li><p><strong>Run in Docker</strong>: If you prefer containerization, you can run SonarScanner within a Docker container. This approach simplifies environment management and ensures consistency across different machines.</p>
</li>
<li><p><strong>Use SonarCloud</strong>: If you're looking for a cloud-based solution, consider using SonarCloud, which offers similar features without the need for local setup. You can connect it to your repository for automated analysis.</p>
</li>
</ul>
</li>
</ul>
<p>That concludes this extensive blog. What I aimed to share with you is:</p>
<ul>
<li><p>What is SonarQube</p>
</li>
<li><p>why and when we use it</p>
</li>
<li><p>Capabilities It Offers</p>
</li>
<li><p>How to Use SonarQube Briefly (We will cover in detail in a separate blog)</p>
</li>
</ul>
<h2 id="heading-summary">Summary:</h2>
<ul>
<li><p><strong>What is SonarQube?</strong>: SonarQube is a code quality and security analysis tool that also functions as a Static Application Security Testing (SAST) tool.</p>
</li>
<li><p><strong>Why Use SonarQube?</strong>: Integrate SonarQube to avoid technical debt, protect against security vulnerabilities, maintain coding standards, and ensure sufficient test coverage.</p>
</li>
<li><p><strong>Understanding Technical Debt</strong>: Technical debt accumulates when quick fixes are applied instead of following best practices, leading to maintenance challenges down the line.</p>
</li>
<li><p><strong>Capabilities Offered by SonarQube</strong>: Explore features like code smells detection, maintainability ratings, duplicated code detection, and security vulnerability detection.</p>
</li>
<li><p><strong>Code Coverage Analysis</strong>: Measure how much of your code is covered by tests, helping you prioritise testing efforts for improved reliability.</p>
</li>
<li><p><strong>Quality Gates</strong>: Set rules based on metrics like code coverage and bugs to ensure only high-quality code is merged into the main branch.</p>
</li>
<li><p><strong>Quality Profiles</strong>: Customize coding rules for specific programming languages to maintain consistent coding standards across your projects.</p>
</li>
<li><p><strong>Generating Reports with SonarScanner</strong>: Use SonarScanner to analyze your code and send reports to SonarQube for comprehensive analysis.</p>
</li>
<li><p><strong>Multiple Usage Options</strong>: Run SonarScanner locally, integrate it into your CI pipeline, use it in Docker, or leverage SonarCloud for cloud-based analysis.</p>
</li>
</ul>
<h2 id="heading-what-is-next">What is Next ?</h2>
<p>In the next blog, we will explore in detail how to set up the infrastructure for SonarQube, covering topics such as:</p>
<ul>
<li><p>Which edition to use</p>
</li>
<li><p>What type of infrastructure is suitable</p>
</li>
<li><p>How to calculate the resources needed for the SonarQube infrastructure</p>
</li>
<li><p>Setting up SonarQube according to industry standards</p>
</li>
</ul>
<p>Thank you for taking the time to read this blog! I hope you found valuable insights throughout. If you have any feedback or notice any mistakes, please feel free to share in comments—I'm always happy to make corrections.</p>
<p>Happy learning, and I look forward to seeing you in the next blog!</p>
]]></content:encoded></item><item><title><![CDATA[Exploring Kubecost: Variants, Alternatives, and More]]></title><description><![CDATA[Kubecost offers a self-hosted variants which we will install in our cluster. Beyond Self-Hosted Kubecost, KubeCost also offers Cloud version, even in Cloud version we have a free tier and Enterprise Tier. You can see the details by clicking here.
Bey...]]></description><link>https://claybrainer.com/exploring-kubecost-variants-alternatives-and-more</link><guid isPermaLink="true">https://claybrainer.com/exploring-kubecost-variants-alternatives-and-more</guid><category><![CDATA[kubecost alternatives]]></category><category><![CDATA[Kubecost installation on EKS]]></category><category><![CDATA[Kubecost]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Mon, 14 Oct 2024 13:08:44 GMT</pubDate><content:encoded><![CDATA[<p>Kubecost offers a self-hosted variants which we will install in our cluster. Beyond Self-Hosted Kubecost, KubeCost also offers Cloud version, even in Cloud version we have a free tier and Enterprise Tier. You can see the details by <a target="_blank" href="https://www.kubecost.com/pricing/?plans=cloud">clicking here</a>.</p>
<p>Beyond this there is also a fully OpenSource version of KubeCost which is called OpenCost which is under Apache 2 License and managed by CNCF community. Kubecost is build on this OpenCost. What is the difference then ? Here we go</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature/Visibility</td><td>Kubecost</td><td>OpenCost</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Type</strong></td><td>Paid with Open Source options</td><td>Open Source</td></tr>
<tr>
<td><strong>Cost Visibility</strong></td><td>Comprehensive, including CPU, memory, storage, network costs</td><td>Basic for CPU, memory, and storage</td></tr>
<tr>
<td><strong>Savings Recommendations</strong></td><td>Provides optimization suggestions to reduce costs</td><td>None</td></tr>
<tr>
<td><strong>Budgets and Alerts</strong></td><td>Users can set budgets and receive alerts for overspending</td><td>Not available</td></tr>
<tr>
<td><strong>External Service Cost Allocation</strong></td><td>Integrates with cloud services (AWS, GCP, Azure) for cost tracking</td><td>Limited to Kubernetes metrics only</td></tr>
<tr>
<td><strong>Network Cost Allocation</strong></td><td>Granular breakdown of network traffic costs</td><td>Lacks detailed network cost monitoring</td></tr>
<tr>
<td><strong>Historical Cost Analysis</strong></td><td>Detailed historical data for analyzing cost trends</td><td>Limited historical data retention</td></tr>
<tr>
<td><strong>Multi-Cluster Cost Tracking</strong></td><td>Supports cost tracking across multiple clusters</td><td>Primarily focused on single cluster</td></tr>
</tbody>
</table>
</div>]]></content:encoded></item><item><title><![CDATA[A Step-by-Step Guide to Setting Up Kubecost on EKS Cluster]]></title><description><![CDATA[Before diving into the installation steps, let’s take a moment for a quick refresher on Kubecost. While you’re probably already familiar with it, a brief introduction will set the stage perfectly and ensure we’re all on the same page for a smooth sta...]]></description><link>https://claybrainer.com/a-step-by-step-guide-to-setting-up-kubecost-on-eks-cluster</link><guid isPermaLink="true">https://claybrainer.com/a-step-by-step-guide-to-setting-up-kubecost-on-eks-cluster</guid><category><![CDATA[kubecost installation]]></category><category><![CDATA[step by step guide to install kubecost]]></category><category><![CDATA[Kubecost installation on EKS]]></category><category><![CDATA[How to install Kubecost on Amazon EKS]]></category><category><![CDATA[Kubecost dashboard access]]></category><category><![CDATA[Kubecost cost analysis tool]]></category><category><![CDATA[Kubernetes cost management]]></category><category><![CDATA[Optimize Kubernetes cloud costs]]></category><category><![CDATA[Kubecost Helm chart installation]]></category><category><![CDATA[Expose Kubecost dashboard public network]]></category><category><![CDATA[Kubernetes Ingress for Kubecost]]></category><category><![CDATA[Amazon EKS cost monitoring]]></category><category><![CDATA[Cost analyzer for Kubernetes clusters]]></category><category><![CDATA[EKS Kubecost setup tutorial]]></category><category><![CDATA[Kubecost]]></category><dc:creator><![CDATA[NaveenKumar VR]]></dc:creator><pubDate>Sun, 29 Sep 2024 09:17:16 GMT</pubDate><content:encoded><![CDATA[<p>Before diving into the installation steps, let’s take a moment for a quick refresher on Kubecost. While you’re probably already familiar with it, a brief introduction will set the stage perfectly and ensure we’re all on the same page for a smooth start!</p>
<h2 id="heading-what-is-kubecosthttpsdocskubecostcom">What is <a target="_blank" href="https://docs.kubecost.com/">KubeCost:</a></h2>
<p>In simple terms, <a target="_blank" href="https://docs.kubecost.com/">Kubecost</a> is a tool that helps you understand and manage the costs of running applications on Kubernetes.</p>
<p>It shows you how much you're spending on things like CPU, memory, and storage, so you can see where your money is going and make sure you're not wasting resources. It's like a budget tracker for your cloud infrastructure specifically for Kubernetes clusters, helping you optimize and save money.</p>
<h2 id="heading-how-much-does-it-cost-to-use-kubecost">How much does it cost to use Kubecost?</h2>
<p>The basic version of Kubecost is free and covers most of our essential needs. However, there is also an Enterprise version that offers additional features. You can view the differences by <a target="_blank" href="https://www.kubecost.com/pricing/?plans=self-hosted"><strong>clicking here</strong>.</a></p>
<p>Additionally, Kubecost offers a Cloud version and a fully open-source version called OpenCost. I don't want to overwhelm you with the details here, as this blog focuses on installing Kubecost on an EKS cluster. If you're interested in learning more about the differences, please <a class="post-section-overview" href="###exploring-kubecost--variants--alternatives--and-more"><strong>click here</strong></a>.</p>
<h2 id="heading-lets-see-how-to-install-kubecost-on-amazon-eks-cluster">Lets see how to install Kubecost on Amazon EKS Cluster:</h2>
<h3 id="heading-pre-requisites">Pre-Requisites:</h3>
<ul>
<li><p>Before we begin, please ensure you have access to a computer that can connect to AWS. Additionally, you’ll need the following permissions:</p>
<ul>
<li><p>The ability to <strong>create IAM roles</strong> in AWS.</p>
</li>
<li><p><strong>Necessary access</strong> to create, delete, and modify resources within your AWS account.</p>
</li>
</ul>
</li>
</ul>
<p>    Let’s get started!</p>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html"><strong>Installing EKSCTL</strong></a>:</p>
<ul>
<li><p>What is EKSCTL? <code>eksctl</code> is a simple command-line tool for creating and managing Amazon EKS (Elastic Kubernetes Service) clusters. It automates tasks like setting up node groups and IAM roles, making it easier to deploy Kubernetes on AWS.</p>
</li>
<li><p>Installing EKSCTL</p>
<ul>
<li><pre><code class="lang-bash">    <span class="hljs-comment"># for ARM systems, set ARCH to: `arm64`, `armv6` or `armv7`</span>
    ARCH=amd64
    PLATFORM=$(uname -s)_<span class="hljs-variable">$ARCH</span>
    curl -sLO <span class="hljs-string">"https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_<span class="hljs-variable">$PLATFORM</span>.tar.gz"</span>
    <span class="hljs-comment"># (Optional) Verify checksum</span>
    curl -sL <span class="hljs-string">"https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_checksums.txt"</span> | grep <span class="hljs-variable">$PLATFORM</span> | sha256sum --check

    tar -xzf eksctl_<span class="hljs-variable">$PLATFORM</span>.tar.gz -C /tmp &amp;&amp; rm eksctl_<span class="hljs-variable">$PLATFORM</span>.tar.gz
    sudo mv /tmp/eksctl /usr/<span class="hljs-built_in">local</span>/bin
    <span class="hljs-comment"># To check the installation and version</span>
    eksctl version <span class="hljs-comment">#This will display the installed version of EKSCTL</span>
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Install AWSCLI (Optional)</strong></p>
<ul>
<li>If you don’t have awscli use the <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#:~:text=installer%20%2D%20Linux%20x86%20\(-,64,-%2Dbit\)">link</a> to install it.. Please make sure you select the right architecture for installation.</li>
</ul>
</li>
<li><p><strong>Establishing Connection to AWS Cloud via awscli</strong></p>
<ul>
<li><p>Note: When you create a user, an access key and secret access key ID will be generated. If you're using a corporate account, you can find these details on your AWS Console login page. For corporate accounts, make sure to select <strong>AWS Programmatic Access</strong> to view your access key and secret key.</p>
</li>
<li><p>I don't want you to navigate through multiple pages, so I've provided the command right here. However, you can also find these commands further down on <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/install-awscli.html">this page</a>.</p>
</li>
<li><pre><code class="lang-bash">    aws configure
    <span class="hljs-comment">#On the following questions add fill the details</span>
    AWS Access Key ID [None]: &lt;Your Access key&gt;
    AWS Secret Access Key [None]: &lt;Your Secret access key id&gt;
    Default region name [None]: &lt;region-code&gt;
    Default output format [None]: json

    <span class="hljs-comment">############## Optional ################</span>
    <span class="hljs-comment">#If you are connecting throuh Session based access use below command</span>
    aws sts get-session-token --duration-seconds 3600 <span class="hljs-comment">#Optional</span>
    <span class="hljs-comment"># You will be getting this output</span>
    {
     <span class="hljs-string">"Credentials"</span>: {
                    <span class="hljs-string">"AccessKeyId"</span>: <span class="hljs-string">"&lt;Accesskey&gt;"</span>,
                    <span class="hljs-string">"SecretAccessKey"</span>: <span class="hljs-string">"&lt;SecretKEY&gt;"</span>,
                    <span class="hljs-string">"SessionToken"</span>: <span class="hljs-string">"&lt;Session Token&gt;"</span>,
                    <span class="hljs-string">"Expiration"</span>: <span class="hljs-string">"2023-02-17T03:14:24+00:00"</span>
                }
    }
    <span class="hljs-comment">############## Optional ################</span>

    <span class="hljs-comment">#Finally to verify the identity</span>
    aws sts get-caller-identity
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Installing EKS Cluster (Optional)</strong></p>
<ul>
<li>Assuming you already have an Amazon EKS cluster deployed and running on AWS, you can proceed. If you haven't set up your cluster yet, please follow the detailed steps provided in <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html">this link</a>.</li>
</ul>
</li>
<li><p><strong>Installing Kubectl</strong></p>
<ul>
<li>If you haven't installed <code>kubectl</code> yet, don't worry! You can easily follow the steps provided in <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html">this link</a> to get it set up on your machine.</li>
</ul>
</li>
<li><p><strong>Installing Helm</strong></p>
<ul>
<li><pre><code class="lang-console">    curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
    chmod 700 get_helm.sh
    ./get_helm.sh
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Update KubeConfig:</strong></p>
<ul>
<li><p>To access your Amazon EKS cluster using <code>kubectl</code>, you need to update your KubeConfig. Here’s how to do it:</p>
<ul>
<li><pre><code class="lang-bash">    <span class="hljs-built_in">export</span> CLUSTER_NAME=<span class="hljs-string">"&lt;YOUR CLUSTER NAME&gt;"</span>

    <span class="hljs-comment">#Update KubeConfig</span>
    aws eks update-kubeconfig --name <span class="hljs-variable">${CLUSTER_NAME}</span> --region <span class="hljs-variable">${AWS_REGION}</span>

    <span class="hljs-comment">#Check whether you can able to connect to cluster</span>
    kubectl get pods
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Configuring EBS CSI Driver on EKS Cluster</strong></p>
<ul>
<li><p><strong>Why it is needed ?</strong> The Amazon EBS CSI driver is required for EKS clusters on version 1.23 and above because Kubernetes has deprecated in-tree storage drivers. The CSI driver provides a standardised, cloud-independent way to manage storage, ensuring EKS can handle EBS volumes for persistent storage after the in-tree drivers are removed. It allows for better flexibility, security, and maintenance.</p>
</li>
<li><p>So Lets configure EBS CSI Driver</p>
</li>
<li><p><strong>Create IAM Role and Service Account:</strong></p>
<ul>
<li><p>EKS uses this service account to perform all the operation in EBS via EBS CSI Driver. So we are creating a Service account on EKS and attaching it with the IAM policy “<strong>AmazonEBSCSIDriverPolicy</strong>”</p>
</li>
<li><pre><code class="lang-bash">    <span class="hljs-comment"># Get the cluster name</span>
    <span class="hljs-built_in">export</span> CLUSTER_NAME=<span class="hljs-string">"&lt;YOUR CLUSTER NAME&gt;"</span>

    <span class="hljs-comment"># Create IAM and attach to service account</span>
    eksctl create iamserviceaccount \
        --name ebs-csi-controller-sa \
        --namespace kube-system \
        --cluster <span class="hljs-variable">${CLUSTER_NAME}</span> \
        --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
        --override-existing-serviceaccounts \
        --approve \
        --role-only \
        --role-name AmazonEKS_EBS_CSI_DriverRole

    <span class="hljs-comment"># Get the newly created service account ARN</span>
    <span class="hljs-built_in">export</span> SERVICE_ACCOUNT_ROLE_ARN=$(aws iam get-role --role-name AmazonEKS_EBS_CSI_DriverRole | jq -r <span class="hljs-string">'.Role.Arn'</span>)

    <span class="hljs-comment"># Installing EBS CSI Driver add-on on our EKS Cluster</span>
    eksctl create addon --name aws-ebs-csi-driver --cluster <span class="hljs-variable">$CLUSTER_NAME</span> \
        --service-account-role-arn <span class="hljs-variable">$SERVICE_ACCOUNT_ROLE_ARN</span> --force
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Check whether the service account is attached with the IAM role.</strong></p>
<ul>
<li><p>Find the Role ARN for <code>AmazonEBSCSIDriverPolicy</code>:</p>
<ul>
<li><p>Open the AWS Console and search for <strong>IAM</strong>.</p>
</li>
<li><p>In the IAM dashboard, click on <strong>Roles</strong> from the left-side menu.</p>
</li>
<li><p>Search for the role named <strong>AmazonEKS_EBS_CSI_DriverRole</strong>.</p>
</li>
<li><p>Open the role and copy the ARN, then save it in a notepad for easy reference.</p>
</li>
</ul>
</li>
<li><p>Check the Service Account:</p>
<ul>
<li><p>The service account <code>ebs-csi-controller-sa</code> is created under the <code>kube-system</code> namespace.</p>
</li>
<li><p>Use the following command to verify whether the service account is attached to the Role ARN:</p>
<pre><code class="lang-bash">  kubectl get serviceaccount ebs-csi-controller-sa -n kube-system -oyaml
</code></pre>
</li>
</ul>
</li>
<li><p>In the output, look for the <code>role</code> or <code>roleARN</code> section, which should contain the ARN you copied earlier. It should look something like this:</p>
<ul>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727525268959/af6374d4-84a8-46e4-ba94-df6503b0844d.png" alt class="image--center mx-auto" /></li>
</ul>
</li>
<li><p>If you do not see the expected output, you may need to edit the service account to include the necessary annotations. Follow these steps:</p>
<ul>
<li><p>Use the following commands to open the service account configuration for editing and locate the <strong>annotations</strong> section (or create it if it doesn't exist) and add the following entry:</p>
</li>
<li><pre><code class="lang-bash">    kubectl edit serviceaccount ebs-csi-controller-sa -n kube-system -oyaml

    <span class="hljs-comment">#Add the below line. Refer the screenshot above</span>
    <span class="hljs-comment"># MAKE SURE INDENTATION ARE POSITION ARE CORRECT</span>
    annotations:
        eks.amazonaws.com/role-arn: &lt;YOUR ROLE ARN&gt;
    <span class="hljs-comment"># Save the file</span>
    <span class="hljs-comment"># Run the below command to confirm the ARN is added</span>
    kubectl get serviceaccount ebs-csi-controller-sa -n kube-system -oyaml
</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-installing-kubecost">Installing KubeCost:</h3>
<ul>
<li><p>To install <strong>Kubecost v2.4.1</strong> using the Helm Chart, simply follow the instructions below:</p>
</li>
<li><p><strong>Check the Latest Version:</strong></p>
<ul>
<li>You can always verify the latest version of Kubecost by visiting the <a target="_blank" href="https://github.com/kubecost/cost-analyzer-helm-chart/releases">official Kubecost releases page</a>. Make sure to update the version number in the installation command accordingly.</li>
</ul>
</li>
<li><p><strong>Install Kubecost:</strong></p>
<ul>
<li><p>Once you’ve confirmed the latest version, proceed with the installation using the following command:</p>
</li>
<li><pre><code class="lang-console">    helm upgrade -i kubecost oci://public.ecr.aws/kubecost/cost-analyzer --version v2.4.1 \
        --namespace kubecost --create-namespace \
        -f https://raw.githubusercontent.com/kubecost/cost-analyzer-helm-chart/develop/cost-analyzer/values-eks-cost-monitoring.yaml
</code></pre>
</li>
</ul>
</li>
<li><p>Confirming Your Kubecost Installation:</p>
<ul>
<li><p>To ensure that Kubecost has been installed successfully, you can verify all components with one command. Simply run the following:</p>
<pre><code class="lang-bash">  <span class="hljs-comment"># Run below command to see the status of the installation</span>
  kubectl get all -n kubecost
</code></pre>
</li>
<li><p>The above command will provide you with a comprehensive view of all resources in the <code>kubecost</code> namespace, including:</p>
<ul>
<li><p><strong>Pods</strong>: Check if all necessary pods are running.</p>
</li>
<li><p><strong>Services</strong>: Verify the status of the Kubecost services, including their external or cluster IPs.</p>
</li>
<li><p><strong>Deployments</strong>: Ensure that the deployments are correctly set up.</p>
</li>
</ul>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727526901056/f5145fc3-3208-4ba2-bb0b-55f865277d2b.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<p>Great ! We've successfully installed Kubecost on our EKS cluster. However, there's a small catch: to access the Dashboard and see the output, you'll need to configure the Deployment to expose it to the public network. Let's get that sorted so you can start exploring!</p>
<h3 id="heading-expose-kubecost-dashboard-to-public-network">Expose Kubecost Dashboard to Public Network:</h3>
<p>When it comes to exposing your Kubecost dashboard, there are two primary methods you can use:</p>
<ol>
<li><p>Directly Configuring LoadBalancer for the Kubecost “KubeAnalyzer” Service</p>
<ul>
<li><p>This approach is straightforward and user-friendly—just set up a LoadBalancer for your service!</p>
</li>
<li><p>However, while it’s simple, it does come with a couple of drawbacks. You’ll incur costs for a dedicated LoadBalancer for this specific service, and more importantly, your service will be directly exposed to the public network, which isn’t the best practice from a security standpoint.</p>
</li>
</ul>
</li>
<li><p>Using Kubernetes Ingress (NGINX Ingress)</p>
<ul>
<li><p>Alternatively, you can leverage Kubernetes Ingress. Here, we’ll set up an Ingress controller, which acts as a proxy server, routing all your deployment traffic efficiently.</p>
</li>
<li><p>With this method, you only need one LoadBalancer for the Ingress controller, while all your deployment services remain behind Cluster IPs (private IPs). The Ingress controller will manage public traffic and intelligently route it to the appropriate services based on your configuration.</p>
</li>
</ul>
</li>
</ol>
<p>Alright! We’ve covered quite a bit so far, and to keep things clear and manageable, I’ve written a separate blog dedicated to exposing Kubecost to a public network. This way, you can follow the detailed steps without feeling overwhelmed in a single post. Want to dive deeper into the process and access your Kubecost dashboard seamlessly? Head over to my in-depth guide by <a target="_blank" href="https://claybrainer.com/exposing-the-kubecost-dashboard-to-a-public-network-a-practical-guide"><strong>clicking here</strong></a>!</p>
<h3 id="heading-credits"><strong>Credits:</strong></h3>
<p>I would like to express my gratitude to the following sources that provided invaluable insights and references in the creation of this blog:</p>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/"><strong>https://docs.aws.amazon.com/</strong></a></p>
</li>
<li><p><a target="_blank" href="https://docs.kubecost.com/"><strong>https://docs.kubecost.com/</strong></a></p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/ingress/"><strong>https://kubernetes.io/docs</strong></a>/</p>
</li>
<li><p><a target="_blank" href="https://archive.eksworkshop.com/"><strong>https://archive.eksworkshop.com/</strong></a></p>
</li>
</ul>
<h3 id="heading-feedback-amp-clarifications"><strong>Feedback &amp; Clarifications</strong></h3>
<p>I hope you found this guide helpful! If you have any questions, need further clarification, or spot any corrections, feel free to leave a comment below. I’d love to hear your feedback and will be happy to assist with any concerns or improvements! 😊</p>
]]></content:encoded></item></channel></rss>