<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Eti Ijeoma - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Eti Ijeoma - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 22:23:52 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/Omah/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Understanding Escape Analysis in Go – Explained with Example Code ]]>
                </title>
                <description>
                    <![CDATA[ In most languages, the stack and heap are two ways a program stores data in memory, managed by the language runtime. Each is optimized for different use cases, such as fast access or flexible lifetimes. Go follows the same model, but you usually don’... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/understanding-escape-analysis-in-go/</link>
                <guid isPermaLink="false">698e1c7090ca92017618cb24</guid>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ golang ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ heap ]]>
                    </category>
                
                    <category>
                        <![CDATA[ escape analysis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory-management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Thu, 12 Feb 2026 18:31:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770921059389/c45b42cb-8cff-4de5-b3d1-7c7adad402c5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In most languages, the stack and heap are two ways a program stores data in memory, managed by the language runtime. Each is optimized for different use cases, such as fast access or flexible lifetimes.</p>
<p>Go follows the same model, but you usually don’t decide between the stack and the heap directly. Instead, the Go compiler decides where values live. If the compiler can prove a value is only needed within the current function call, it can keep it on the stack. If it cannot prove that, the value “escapes” and is placed on the heap. This technique is called <strong>escape analysis</strong>.</p>
<p>This matters because heap allocations increase garbage collector work. In code that runs often, that extra work can show up as more CPU spent in GC, more allocations, and less predictable performance.</p>
<p>In this article, I’ll explain what escape analysis is, the common patterns that trigger heap allocation, and how to confirm and reduce avoidable allocations.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-do-you-really-need-to-care-about-escape-analysis">Do You Really Need to Care About Escape Analysis?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-memory-layout-and-lifecycle">Memory Layout and Lifecycle</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sharing-down-and-sharing-up">Sharing Down and Sharing Up</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-escape-analysis-in-practice">Escape Analysis in Practice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-escape-analysis-to-guide-performance">How to Use Escape Analysis to Guide Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-further-reading">Further Reading</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<ul>
<li><p>Familiarity with Go fundamentals (functions, variables, structs, slices, maps)</p>
</li>
<li><p>Basic understanding of pointers in Go (<code>&amp;</code> and <code>*</code>)</p>
</li>
<li><p>A general idea of how goroutines work</p>
</li>
</ul>
<h2 id="heading-do-you-really-need-to-care-about-escape-analysis">Do You Really Need to Care About Escape Analysis?</h2>
<p>Before we go deeper, I want to call this out clearly. For the correctness of your program, it doesn’t matter whether a variable lives on the stack or on the heap, or whether you know that detail. The Go compiler is smart enough to place values where they need to be so that your program behaves correctly.</p>
<p>Most of the time, you don’t need to think about this at all. It only starts to matter when performance becomes a problem. If your program is already fast enough, you’re done, and there’s no point trying to squeeze out extra speed.</p>
<p>You should only start caring about stack vs heap when you have benchmarks that show your program is too slow, and those same benchmarks point to heavy heap allocation and garbage collection as part of the problem.</p>
<h2 id="heading-memory-layout-and-lifecycle"><strong>Memory Layout and Lifecycle</strong></h2>
<p>To get a better understanding of what escape analysis is, you first need a simple picture of how Go lays out memory while your program runs. At this level, it comes down to the stack each goroutine uses, how stack frames are carved out of that stack, and when values move to the heap where the garbage collector can see them.</p>
<h3 id="heading-goroutine-stacks-and-stack-frames">Goroutine Stacks and Stack Frames</h3>
<p>When a Go program starts, the runtime creates the <code>main</code> goroutine, and every <code>go</code> statement creates a new goroutine, each with its own stack.</p>
<p>There’s not a single global stack for the whole process. As of writing this article, with Go v1.25.7, each goroutine gets an initial contiguous block of 2,048 bytes of memory, which acts as its stack. The stack is where Go stores data that belongs to function calls. When a goroutine calls a function, Go reserves a chunk of that goroutine’s stack for the function’s local data. That chunk is called a <strong>stack frame</strong>.</p>
<p>It holds the function’s local variables and the call state needed to return and continue execution. If that function calls another function, a new frame is added on top. When the inner function returns, its frame becomes invalid, and the goroutine continues in the caller’s frame.</p>
<p>A stack frame only lives for as long as the function is active. Once the function returns, anything stored in its frame is considered invalid, even if the raw bytes are still in memory and will be reused later. Code must not rely on those values after the return</p>
<p>Go stacks can grow. A goroutine starts with a small stack and the runtime grows it when needed, but the lifetime rule stays the same. A value is safe in a stack frame only if nothing can still reference it after the function returns. If it might be referenced later, it can’t stay in that frame and must be placed somewhere safer.</p>
<h3 id="heading-pointers-and-lifetime">Pointers and Lifetime</h3>
<p>In Go, taking an address like <code>p := &amp;x</code> means you now have a pointer in one stack frame that refers to a value which may have been created in another frame. When you pass that pointer into a function, Go still passes by value. The callee gets its own pointer variable on its own stack frame, but the address inside still points to the same underlying value. So pointers are how you share access to one value across several frames without copying the value itself.</p>
<p>Lifetime becomes important when a pointer can outlive the frame where the pointed value was created. As long as both the pointer and the value live inside frames that are still active in the current call stack, everything is safe.</p>
<p>Once a pointer might still exist after the original frame has returned, the value can no longer stay in that frame, because that frame will become invalid. At that point, the value has to be placed in a safer location so that no pointer ever points into dead stack memory.</p>
<h2 id="heading-sharing-down-and-sharing-up">Sharing Down and Sharing Up</h2>
<p>Now that you have a picture of stacks, frames, and pointers, we can look at two common ways pointers move through your code. I’ll call them sharing down and sharing up. The names aren’t special Go terms. They’re just a simple way to describe how a pointer moves along the call stack.</p>
<h3 id="heading-sharing-down">Sharing Down</h3>
<p>Sharing down means a function passes a pointer or reference to functions it calls. The pointer moves deeper into the call stack, but the value it points to still belongs to a frame that is active.</p>
<p>Example 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-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    n := <span class="hljs-number">10</span>
    multiply(&amp;n) 
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">multiply</span><span class="hljs-params">(v *<span class="hljs-keyword">int</span>)</span></span> {
   *v = *v * <span class="hljs-number">2</span>
}
</code></pre>
<p>In <code>main</code>, you take the address of <code>n</code> and pass it into <code>multiply</code>. While <code>multiply</code> runs, both the <code>main</code> frame and the <code>multiply</code> frames are active. The pointer in <code>multiply</code> points to a value that still lives in an active frame, so this situation is safe from a lifetime point of view.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770153560595/0681b535-3668-406d-acaf-6e27679d52e1.png" alt="Diagram showing two stack frames on one goroutine, with the upper frame pointing to a value in the lower frame to illustrate sharing down on the stack" class="image--center mx-auto" width="1252" height="1102" loading="lazy"></p>
<p>In the diagram below, after the <code>multiply</code> function runs and returns, the <code>multiply</code> frame becomes invalid, and we don’t need to do anything because the stack pointer is simply popped back to the previous frame's address. This action automatically reclaims all the memory used by that function in one step, so the garbage collector is not involved in cleaning up stack memory</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770153730555/9093b49a-a8bc-497d-be06-77738a89a6c4.png" alt="Diagram showing two stack frames with a value in the upper frame updated through a pointer stored in the lower frame, again illustrating sharing down entirely on the stack" class="image--center mx-auto" width="1346" height="1130" loading="lazy"></p>
<h3 id="heading-sharing-up">Sharing Up</h3>
<p>Sharing up means a function returns a pointer, or stores it somewhere that will still be around after the function returns. The pointer moves back up the call stack or into some longer-lived state while the frame that created the value is about to end, so that value can no longer be tied to that one frame.</p>
<p>The same idea shows up when you share a value with another goroutine, because Go doesn’t let one goroutine hold pointers into another goroutine’s stack, so shared data needs a lifetime that is not tied to a single stack.</p>
<h4 id="heading-heap-garbage-collection-and-lifetime">Heap, garbage collection, and lifetime</h4>
<p>Values that might outlive a single stack frame can’t stay in that frame. The compiler places them on the heap instead. The heap is a separate region of memory that isn’t tied to one function call. Any goroutine can hold pointers to heap values, and those values stay valid as long as something in the program can still reach them. You can think of the heap as storage for “<em>might live longer than this call</em>”.</p>
<p>The garbage collector is what keeps this safe. Periodically, the runtime starts from a set of roots (global variables, active stack frames, some internal state) and follows all the pointers it can see. Any heap value that is still reachable is kept. Any heap value that is no longer reachable is treated as garbage and its memory is reclaimed.</p>
<p>This means a pointer in <code>main</code> will never legally point into dead stack memory. Either the value stayed in an active frame, or it was placed on the heap where the GC can track its lifetime. The tradeoff is that more heap allocations and longer-lived objects require the GC to do more work.</p>
<p>Here’s an example:</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-keyword">type</span> Car <span class="hljs-keyword">struct</span> {
    Brand <span class="hljs-keyword">string</span>
    Model <span class="hljs-keyword">string</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">// main receives a pointer from a function it called and this is sharing up</span>
    carPtr := makeCar(<span class="hljs-string">"Volkswagen"</span>, <span class="hljs-string">"Golf"</span>) 

    fmt.Printf(<span class="hljs-string">"I received a car: %s %s\n"</span>, carPtr.Brand, carPtr.Model)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">makeCar</span><span class="hljs-params">(b, m <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Car</span></span> {
    myCar := Car{
        Brand: b,
        Model: m,
    }
    <span class="hljs-keyword">return</span> &amp;myCar
}
</code></pre>
<p>In the above code:</p>
<ol>
<li><p>In <code>makeCar</code> (the callee frame), Go creates a local variable <code>myCar</code>. Because you return <code>&amp;myCar</code>, the compiler allocates the <code>Car</code> value on the heap, and let’s <code>myCar</code> hold the heap address <code>0xc00029fa0</code>.</p>
</li>
<li><p>When <code>makeCar</code> returns, that address is copied into <code>carPtr</code> in <code>main</code> (the top frame). <code>carPtr</code> is just another stack variable, but its value is still <code>0xc00029fa0</code>, so now <code>main</code> also points to the same heap <code>Car</code>.</p>
</li>
<li><p>On the right, the heap bubble shows the actual <code>Car</code> value at <code>0xc00029fa0</code>. Both <code>car</code> (while <code>makeCar</code> is running) and <code>carPtr</code> (after it returns) reach that same value through their pointers.</p>
</li>
<li><p>Once <code>makeCar</code> is done, its frame drops into the “invalid memory” region, but the <code>Car</code> stays alive on the heap because <code>main</code> still holds <code>carPtr</code>. That’s the escape: the value stops being tied to the callee frame and gets heap lifetime instead.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770255008545/5ef80c9b-3203-4ca3-a26e-f1e2462912cf.png" alt="Diagram showing a caller and callee stack frame both holding a pointer to the same value in heap memory, illustrating a value being shared up and escaping the stack" class="image--center mx-auto" width="1850" height="1190" loading="lazy"></p>
<h2 id="heading-escape-analysis-in-practice">Escape Analysis in Practice</h2>
<p>Escape analysis is how the Go compiler decides whether a value lives on the stack or on the heap. It’s not only about returning pointers – it follows how addresses move through your code. If a value might outlive the current function, the compiler can’t keep it in that stack frame and moves it to the heap. Since only the compiler sees the full picture, the useful thing is to ask it to show these decisions and then link them back to your code.</p>
<p>To do that, we can pass compiler flags using <code>-gcflags</code> when running <code>go build</code> or <code>go run</code>. If you want to see the available options, you can check <code>go tool compile -h</code>. In that list, <code>-m</code> prints the compiler’s optimisation decisions, including escape analysis output. If you want more details, you can use <code>-m=2</code> or <code>-m=3</code> for a more verbose output. The <code>-l</code> flag disables inlining, so the report is easier to read because the compiler is not merging small functions into their callers.</p>
<p>So, the command will look like this:</p>
<pre><code class="lang-bash">go run -gcflags=<span class="hljs-string">'all=-m -l'</span> .
</code></pre>
<p>Or for a build:</p>
<pre><code class="lang-bash">go build -gcflags=<span class="hljs-string">'all=-m -l'</span> .
</code></pre>
<h2 id="heading-how-to-use-escape-analysis-to-guide-performance">How to Use Escape Analysis to Guide Performance</h2>
<p>You can think of escape analysis as the thing that turns your code choices into GC work. When a value escapes, it gets heap lifetime, and the garbage collector has to visit it. In hot paths, lots of small escaping values show up as extra GC time and jitter in latency. When a value stays in a stack frame, it becomes invalid and dies with the frame and the GC does not care about it.</p>
<p>Here are five simple practices that help performance without making</p>
<ol>
<li><p><strong>Prefer values for small data:</strong> If the function doesn’t need to mutate the caller’s data, use value types for small structs and basic types when passing arguments and returning results. It’s cheap to copy an <code>int</code> or a small struct, and it often keeps lifetimes local to a single call.</p>
</li>
<li><p><strong>Use pointers when sharing or mutation is part of the design:</strong> opt for pointers when you genuinely need shared mutable state or want to avoid copying large structs.</p>
</li>
<li><p><strong>Avoid creating long-lived references by accident</strong>: Be careful when returning pointers to locals, capturing variables in closures, or storing addresses in long-lived structs, maps, or interfaces. These patterns are the ones most likely to push values out of a stack frame.</p>
</li>
<li><p><strong>Pass in reusable buffers on hot paths</strong>: On code paths that run very often, the problem is usually not one big allocation, but many small ones happening in a loop. A common cause is functions that always create a new buffer inside, even when the caller could have passed one in.</p>
<p> A simple way to cut those extra allocations is to let the caller own the buffer. The caller allocates a <code>[]byte</code> once, then passes it into the function each time. The function only fills the buffer instead of creating a new one.</p>
<p> Here’s an example of how a bad function allocates a new buffer every call:</p>
<pre><code class="lang-go"> <span class="hljs-keyword">package</span> main

 <span class="hljs-comment">// Bad: helper allocates every call.</span>
 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fillBad</span><span class="hljs-params">()</span> []<span class="hljs-title">byte</span></span> {
     buf := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-number">4096</span>)
     <span class="hljs-comment">// pretend we read into it</span>
     buf[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>
     <span class="hljs-keyword">return</span> buf
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hotPathBad</span><span class="hljs-params">()</span></span> {
     <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1</span>_000_000; i++ {
         b := fillBad() <span class="hljs-comment">// allocates 1,000,000 times</span>
         _ = b
     }
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
     hotPathBad()
 }
</code></pre>
<p> When we run escape analysis with this:</p>
<pre><code class="lang-bash"> go run -gcflags=<span class="hljs-string">'-m -l'</span> .
</code></pre>
<p> We see the following:</p>
<pre><code class="lang-plaintext"> ./main.go:5:13: make([]byte, 4096) escapes to heap
</code></pre>
<p> If we were only allocating a few times, we could choose not to worry – but the real problem is how this looks inside the loop. <code>hotPathBad</code> calls <code>fillBad</code> on every iteration, so each call allocates a new 4 KB slice on the heap. If this loop runs many times, you end up creating a lot of short-lived heap objects. The garbage collector then has to find and clean up all those buffers, which adds extra work that you could have avoided by reusing a single buffer.  </p>
<p> Here’s an example of a better version where the caller allocates once and reuses:</p>
<pre><code class="lang-go"> <span class="hljs-keyword">package</span> main

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fill</span><span class="hljs-params">(buf []<span class="hljs-keyword">byte</span>)</span> <span class="hljs-title">int</span></span> {
     <span class="hljs-comment">// pretend we read into it</span>
     buf[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>
     <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">hotPath</span><span class="hljs-params">()</span></span> {
     buf := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-number">4096</span>) 

     <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1</span>_000_000; i++ {
         n := fill(buf) 
         _ = buf[:n]
     }
 }

 <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
     hotPath()
 }
</code></pre>
<p> In this version, <code>hotPath</code> controls the buffer. It allocates <code>buf</code> once, then passes it into <code>fill</code> on every loop. You still read the same data, but you avoid creating a new slice on each call. That reduces avoidable allocations in the hot path.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In Go, where a value ends up is not decided by how you create it. It’s decided by how long that value must remain valid and how it is referenced as your code runs.</p>
<p>The practical takeaway is not to avoid pointers. It’s to be deliberate about lifetime. Value semantics can keep lifetimes tight and reduce GC work, while pointers can be the right choice when you need shared state or in-place updates. The balance is to write the clear version first, then look at your benchmarks and profiles to see if anything actually really needs to change.</p>
<h2 id="heading-further-reading">Further Reading</h2>
<p>Language Mechanics On Stacks And Pointers - William Kennedy</p>
<p><a target="_blank" href="https://go.dev/doc/gc-guide">Go Compiler: Escape Analysis Flaws</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Configure Network Interfaces in Linux ]]>
                </title>
                <description>
                    <![CDATA[ Networking is an essential part of any Linux system. Proper networking allows communication between devices and the internet. Understanding the network interface is vital when setting up servers, solving connectivity issues, and managing device traff... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/configure-network-interfaces-in-linux/</link>
                <guid isPermaLink="false">6850922657a503eb47ff3b2b</guid>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Mon, 16 Jun 2025 21:52:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750110739161/ebf2347c-ac63-4fab-ad2f-5d9229e77eaa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Networking is an essential part of any Linux system. Proper networking allows communication between devices and the internet. Understanding the network interface is vital when setting up servers, solving connectivity issues, and managing device traffic flow.</p>
<p>A common problem faced in networking is losing connectivity after modifying the network settings, which leads to an inability to access the system. This usually happens due to a misconfigured IP address, incorrect settings, and a poor understanding of network interface configurations.</p>
<p>In this article, we’ll guide you through understanding these network interface configurations, setting up and managing network interfaces on Linux, checking available interfaces, configuring static and dynamic IP addresses, and best practices to consider when setting up network interfaces. At the end of this article, you’ll have a solid foundation in network interfaces.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-network-interfaces">What are Network Interfaces?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-types-of-network-interfaces-in-linux">Types of Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-network-interfaces-matter">Why Network Interfaces Matter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-list-network-interfaces-in-linux">How to List Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-network-interfaces-in-linux">How to Configure Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-a-network-bridge-in-linux">How to Set Up a Network Bridge in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-configuring-network-interfaces-in-linux">Best Practices for Configuring Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-are-network-interfaces">What are Network Interfaces?</h2>
<p>A network interface is a connection point within the Linux system that allows communication with other devices within the network<strong>.</strong> It is how the Linux kernel links the software side of the network with the hardware side. Linux systems provide many network interfaces that help to facilitate communication between the system and other external networks. </p>
<p>Linux network interfaces are essential for troubleshooting, configuration, management, and optimization of networking tasks. Understanding what they are and how they work allows you to optimize your server networking and security.</p>
<h2 id="heading-types-of-network-interfaces-in-linux">Types of Network Interfaces in Linux</h2>
<p>Network interfaces can be classified into two main categories: physical and virtual network interfaces.</p>
<h3 id="heading-physical-network-interfaces">Physical Network Interfaces</h3>
<p>Physical network adapters are the hardware components of the network interface that connect the system to a physical network. These physical networks include Wi-Fi and Ethernet. These adapters, commonly called Network Interface Cards (NIC), can be identified by their device names, such as wlan0 and eth0. They include the following:</p>
<ol>
<li><p><strong>Ethernet Interface (eth0, eth1, and so on)</strong></p>
<p> Ethernet interface is used for wired connections via an Ethernet card and helps configure high-speed networking. It can be used in data centres and servers. </p>
</li>
<li><p><strong>Wi-Fi interface (wlan0, wlan1, and so on)</strong></p>
<p> This represents a wireless network adapter, and it enables wireless connectivity via Wi-Fi networks to the servers.</p>
</li>
</ol>
<h3 id="heading-virtual-network-interfaces">Virtual Network Interfaces</h3>
<p>Virtual network interfaces are software-based interfaces managed by the Linux operating system. They integrate network virtualization technologies like Docker or KVM. There are several virtual network interfaces, and the most common ones include:</p>
<ul>
<li><p><strong>Loopback interface</strong>: This is a special interface that allows a system to communicate internally. It is permanently assigned the IP address 127.0.0.1, referred to as the <a target="_blank" href="http://localhost">localhost</a>.</p>
</li>
<li><p><strong>Bridge Interface</strong>: They are used to connect multiple network interfaces. It is useful for virtualization environments (for example, Linux KVM, Docker networking).</p>
</li>
<li><p><strong>Tunnel Interface</strong>: This is used for VPNs and networking tunnels. It helps to facilitate the passage of encrypted network traffic.</p>
</li>
</ul>
<h2 id="heading-why-network-interfaces-matter">Why Network Interfaces Matter</h2>
<p>Network interfaces form an essential component of a Linux system. It enables communication between devices and the internet, and properly configuring these interfaces provides the following benefits:</p>
<p><strong>Seamless connectivity</strong>: Network interfaces allow devices to communicate over local networks and the internet, enabling proper data exchange between servers and networks.</p>
<p><strong>Proper network management</strong>: Administrators can configure network interfaces by creating, managing, and assigning static or dynamic IPs and optimizing traffic flow.</p>
<p><strong>Improved security</strong>: Administrators can configure network interfaces with firewalls and VPNs to secure data and prevent unauthorized access.</p>
<p><strong>It provides support for virtualization and containerization</strong>: Virtual network interfaces provide proper communication between virtual machines, Docker containers, and other physical servers. This makes them essential for creating and managing DevOps environments.</p>
<h2 id="heading-how-to-list-network-interfaces-in-linux">How to List Network Interfaces in Linux</h2>
<p>You can check the available network interfaces within the Linux environment using the following commands.</p>
<ol>
<li><p><strong>Using the</strong> <code>ip</code> <strong>command:</strong></p>
<p> To list all network interfaces and their status, you can use the <code>ip link show</code> command. It displays details about the network interfaces, like the name, status, and MAC address.</p>
</li>
<li><p><strong>Using the</strong> <code>ifconfig</code> <strong>command</strong></p>
<p> To list all network interfaces, use this command: <code>ifconfig -a</code>. The command also displays details about the network interfaces and their current state.</p>
</li>
<li><p><strong>Using</strong> <a target="_blank" href="https://networkmanager.dev/docs/api/latest/nmcli.html"><code>nmcli</code></a> <strong>for NetworkManager-controlled systems</strong></p>
<p> To check the status of all network interfaces managed by NetworkManager, run:</p>
<p> <code>nmcli device status</code>.</p>
</li>
<li><p><strong>Using the</strong> <code>/sys/class/net/</code> <strong>directory</strong></p>
<p> To list all network interfaces, run <code>ls /sys/class/net/</code> This command is useful for scripting and automation because it provides a reliable way to check available interfaces programmatically.</p>
</li>
</ol>
<h2 id="heading-how-to-configure-network-interfaces-in-linux">How to Configure Network Interfaces in Linux</h2>
<p>Network interface configuration is essential for managing Linux servers and workstations. Understanding this configuration will help ensure smooth connectivity within your systems. This section will give you the correct information on configuring network interfaces.</p>
<h3 id="heading-assign-a-static-ip-address">Assign a Static IP Address</h3>
<p>A static IP address ensures the device maintains the same IP after each reboot. This is particularly useful for servers and devices that need consistent addressing. To assign a static IP address, the NetworkManager Command Line Interface (<strong>nmcli</strong>) provides a command-line utility to configure the network interface as shown below.</p>
<pre><code class="lang-bash">nmcli connection modify eth0 ipv4.addresses 192.168.1.100/24   <span class="hljs-comment"># set a static IPv4 address and subnet mask</span>

nmcli connection modify eth0 ipv4.gateway 192.168.1.1          <span class="hljs-comment"># define the default gateway</span>

nmcli connection modify eth0 ipv4.dns <span class="hljs-string">"8.8.8.8 8.8.4.4"</span>        <span class="hljs-comment"># configure primary and secondary DNS servers</span>

nmcli connection modify eth0 ipv4.method manual                <span class="hljs-comment"># switch the interface from DHCP to manual mode</span>

nmcli connection up eth0                                       <span class="hljs-comment"># bring the interface down and up to apply changes</span>
</code></pre>
<p>These commands set a fixed IP, gateway, and DNS on eth0, switch the interface to manual mode, and restart it so the new settings take effect. The settings persist across reboots because they are stored by <code>NetworkManager</code></p>
<h3 id="heading-assign-a-temporary-ip-address">Assign a Temporary IP Address</h3>
<p>The <code>ip</code> command lets you configure interfaces dynamically (not persistent across reboots):</p>
<pre><code class="lang-bash">ip addr add 192.168.1.100/24 dev eth0     <span class="hljs-comment"># assign 192.168.1.100/24 to interface eth0 (temporary)</span>

ip route add default via 192.168.1.1      <span class="hljs-comment"># set the default gateway to 192.168.1.1</span>
</code></pre>
<p>These two commands give eth0 the IP <code>192.168.1.100/24</code> and point all outbound traffic to the gateway <code>192.168.1.1</code>. The settings last only until the next reboot or interface reset.</p>
<h3 id="heading-assign-an-ip-address-with-ifconfig-deprecated">Assign an IP Address with ifconfig (deprecated)</h3>
<p>Older systems still ship with <code>ifconfig</code> and <code>route</code>. These commands are also temporary.</p>
<pre><code class="lang-bash">ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up  <span class="hljs-comment"># assign 192.168.1.100/24 to eth0 and bring it up</span>

route add default gw 192.168.1.1 eth0                <span class="hljs-comment"># set the default gateway to 192.168.1.1 via eth0</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> Prefer <code>ip</code> or <code>nmcli</code> on modern systems.</p>
</blockquote>
<h3 id="heading-enable-dhcp-with-nmcli">Enable DHCP with nmcli</h3>
<p>A DHCP-assigned address lets the network hand out an IP address automatically.</p>
<pre><code class="lang-bash">nmcli connection modify eth0 ipv4.method auto   <span class="hljs-comment"># switch eth0 to use DHCP for automatic addressing</span>

nmcli connection up eth0                        <span class="hljs-comment"># restart the connection so the new DHCP setting takes effect</span>
</code></pre>
<p>To renew or request a lease directly:</p>
<pre><code class="lang-bash">dhclient eth0   <span class="hljs-comment"># manually request or renew an IP address via DHCP on interface eth0</span>
</code></pre>
<p>These commands set eth0 to use DHCP, restart the link so the change takes effect, and (optionally) trigger an instant lease renewal.</p>
<h3 id="heading-assign-multiple-ip-addresses-to-one-interface">Assign Multiple IP Addresses to One Interface</h3>
<p>A network interface can have multiple addresses assigned to it, making it applicable to host multiple services on a single interface.</p>
<p><strong>Using IP command (Temporary Assignment)</strong></p>
<pre><code class="lang-bash">ip addr add 192.168.1.101/24 dev eth0   <span class="hljs-comment"># add an extra IPv4 address to eth0 (temporary)</span>

ip addr add 2001:db8::1/64 dev eth0     <span class="hljs-comment"># add an IPv6 address to eth0 (temporary)</span>
</code></pre>
<p>These two commands attach an extra IPv4 and an IPv6 address to eth0 until the interface resets or the system reboots</p>
<p><strong>Persistent Configuration (Netplan)</strong></p>
<p>Edit the <code>/etc/netplan/01-netcfg.yaml</code> file:</p>
<pre><code class="lang-bash">network:

  version: 2

  renderer: networkd

  ethernets:

    eth0:

      addresses:

        - 192.168.1.100/24

        - 192.168.1.101/24

        - 2001:db8::1/64
</code></pre>
<p>After editing the file, run <code>sudo netplan apply</code> to make the additional addresses stick across reboots.</p>
<h2 id="heading-how-to-set-up-a-network-bridge-in-linux">How to Set Up a Network Bridge in Linux</h2>
<p>A network bridge allows multiple interfaces to act as a single network segment, which is useful in virtualization (KVM, Docker).</p>
<p><strong>Using</strong> <code>brctl</code> <strong>(bridge-utils package)</strong></p>
<pre><code class="lang-bash">brctl addbr br0                       <span class="hljs-comment"># create a new bridge interface named br0</span>

brctl addif br0 eth0                  <span class="hljs-comment"># add physical interface eth0 to the bridge</span>

ip addr add 192.168.1.100/24 dev br0  <span class="hljs-comment"># assign an IP address to the bridge, not to eth0</span>

ip link <span class="hljs-built_in">set</span> br0 up                    <span class="hljs-comment"># bring the bridge interface online</span>
</code></pre>
<p>These commands create bridge br0, attach eth0 to it, give the bridge its own IP, and bring it online.</p>
<h4 id="heading-ia"> </h4>
<p><strong>Using nmcli (for NetworkManager-managed systems)</strong></p>
<pre><code class="lang-bash">nmcli connection add <span class="hljs-built_in">type</span> bridge ifname br0                       <span class="hljs-comment"># create a new bridge named br0</span>

nmcli connection modify br0 bridge.stp no                         <span class="hljs-comment"># turn off Spanning Tree Protocol</span>

nmcli connection add <span class="hljs-built_in">type</span> bridge-slave ifname eth0 master br0     <span class="hljs-comment"># attach physical interface eth0 to br0</span>

nmcli connection up br0                                           <span class="hljs-comment"># bring the bridge online so settings take effect</span>
</code></pre>
<p>This sequence builds the same bridge through NetworkManager, disables <a target="_blank" href="https://en.wikipedia.org/wiki/Spanning_Tree_Protocol">STP</a> for faster convergence, links eth0 as a slave, and activates the bridge so guests can reach the network.</p>
<h2 id="heading-best-practices-for-configuring-network-interfaces-in-linux">Best Practices for Configuring Network Interfaces in Linux</h2>
<h3 id="heading-make-your-configurations-persistent"><strong>Make Your Configurations Persistent</strong></h3>
<p>One of the mistakes network engineers make in Linux networking is making changes that do not persist after rebooting. While specific commands can modify the network settings temporarily, they do not save these changes permanently.</p>
<p>To ensure that these network settings survive server reboots, modify system configuration files such as <code>/etc/network/interfaces</code>. Once you ensure that all changes are persistent, there will be no unexpected disruptions when a system restarts.</p>
<h3 id="heading-assign-static-ips-for-servers"><strong>Assign Static IPs for Servers</strong></h3>
<p>Static IP addresses are the best for servers and critical infrastructure. Unlike DHCP addresses, which can change over time, static IP addresses are more stable and reliable. For services like web hosting and database management, static IPs play a key role, as IP addresses do not need to change.</p>
<h3 id="heading-secure-your-network-interfaces"><strong>Secure Your Network Interfaces</strong></h3>
<p>Network interfaces are the entry points into a system, so if they are misconfigured, they could pose a considerable security risk. To reduce attacks, administrators should turn off all unused network interfaces by modifying the configuration file to prevent automatic activation. Additionally, you should use firewall tools to control the traffic that tries to reach the system.</p>
<h3 id="heading-monitor-your-network-interfaces"><strong>Monitor Your Network Interfaces</strong></h3>
<p>As a system administrator, monitoring network interfaces helps prevent downtime and ensure proper network reliability. You can check the status of your network interfaces by running commands like <code>link show</code> or <code>if-config -a</code>. You can also monitor them in real time using tools like Netstat. Monitoring your systems ensures that network issues are detected early enough, reducing downtime and improving network stability.</p>
<h3 id="heading-constantly-update-network-packages"><strong>Constantly Update Network Packages</strong></h3>
<p>You must constantly update network management tools and drivers because it helps to implement security patches and other performance improvements, as outdated network packages can cause security vulnerabilities. There are specific network-related packages such as <code>network-manager</code>, <code>bridge-utils</code> and <code>iproute2</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Setting up network interfaces in Linux is a fundamental skill every system administrator should have. Whether configuring static IP addresses or enabling DHCP, understanding these concepts will ensure that your systems are stable and have proper connectivity. Implementing best practices like monitoring traffic and securing the network interface gives you the best results. As you continue working with Linux, you can experiment with different configurations to deepen your understanding of network interfaces.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up a Kubernetes Network Policy and Secure Your Cluster ]]>
                </title>
                <description>
                    <![CDATA[ In a Kubernetes environment, proper networking allows for seamless communication between various components within the cluster and the external environment. As your applications grow, networking becomes more and more important and helps ensure that t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/set-up-kubernetes-network-policy-and-secure-your-cluster/</link>
                <guid isPermaLink="false">67b49d275027eb87a4a1ba21</guid>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clusters ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Tue, 18 Feb 2025 14:45:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739889943803/796d97e8-a1c9-41e4-a678-61477514c020.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In a Kubernetes environment, proper networking allows for seamless communication between various components within the cluster and the external environment. As your applications grow, networking becomes more and more important and helps ensure that the application is scalable and secure enough to meet your users’ demands.</p>
<p>Kubernetes networking helps you manage how pods, services, and other external entities interact in this environment to ensure proper connectivity, isolation, and load distribution where necessary. It offers a flexible yet sophisticated networking system that implements fine-grained security controls through Network Policies.</p>
<p>One unique feature of Kubernetes is that it lets you deploy and manage multiple applications at scale within a single cluster. This helps you manage resources efficiently and optimizes costs as your applications run. But this also introduces challenges related to resource isolation and security. This is where proper Kubernetes networking becomes essential.  </p>
<p>In this article, we will discuss the fundamentals of Kubernetes networking and how it facilitates secure connections within a cluster. We will also explore Network Policies as a mechanism for defining rules that regulate pod-to-pod and pod-to-external communication, ensuring fine-grained control over traffic flow within the cluster.</p>
<h3 id="heading-heres-what-well-cover"><strong>Here’s what we’ll cover:</strong></h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-breakdown-of-network-connectivity-types">Breakdown of Network Connectivity Types</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-kubernetes-network-policies">What Are Kubernetes Network Policies?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-kubernetes-network-policies-work">How Kubernetes Network Policies Work</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-networking-policies">How to Implement Networking Policies</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-a-simple-kubernetes-network-policy-on-eks">How to Set Up a Simple Kubernetes Network Policy on EKS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-and-why-to-use-kubernetes-network-policies">When and Why to Use Kubernetes Network Policies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-implementing-kubernetes-network-policies-on-your-cluster">Best practices for Implementing Kubernetes Network Policies on your cluster</a></p>
</li>
</ol>
<h2 id="heading-breakdown-of-network-connectivity-types"><strong>Breakdown of Network Connectivity Types</strong></h2>
<p>Kubernetes networking is designed to achieve four important goals within the Kubernetes environment to ensure the seamless operation of a Kubernetes cluster. These goals are set to ensure that there is proper communication between the containers, pods, and external entities, enabling them to work together effectively within the Kubernetes infrastructure.</p>
<h3 id="heading-container-to-container-communication"><strong>Container-to-Container Communication</strong></h3>
<p>One of the goals of implementing proper Kubernetes networking is to allow containers within the same pod to communicate directly with each other. Sharing the same networking namespace allows these containers to interact with each other using localhost, resulting in low-latency communication that helps multi-container applications function properly.</p>
<p>Container-to-container communication is useful when working with workloads that have tightly coupled processes and need to communicate quickly without latency within a single pod.</p>
<h3 id="heading-pod-to-pod-connectivity"><strong>Pod-to-Pod connectivity</strong></h3>
<p>Within a Kubernetes environment, pods are assigned unique IP addresses, making pod-to-pod communication simple and straightforward. Understanding traditional networking between servers, Kubernetes removes the complexity of Network Address Translation (NAT), enabling pods to communicate with ease.</p>
<p>Pod-to-pod communication is the backbone of microservices architecture, allowing each pod to operate independently while remaining connected to others.</p>
<h3 id="heading-pod-to-service-interaction"><strong>Pod-to-Service Interaction</strong></h3>
<p>Services in Kubernetes are often described as stable endpoints that help pods access each other. They ensure that traffic is routed to the right pod, regardless of the complexity of the pod setup. Service-to-pod communication is typically reliable, especially in environments where traffic and pod configurations are constantly evolving.</p>
<h3 id="heading-external-to-internal-access"><strong>External-to-Internal Access</strong></h3>
<p>One of the goals of Kubernetes networking is also to manage traffic that comes from outside the cluster. There are several tools, like Ingress Controllers and LoadBalancers, that help handle external-to-internal communication. These tools help ensure that the right application is exposed to end-users, ensuring the proper delivery of services.</p>
<p>While Kubernetes networking meets the requirements of these goals, communication is usually open-ended by default. This means that pods within a cluster can freely communicate with each other without any restriction. This is not ideal, especially in a production environment where isolation and security are important. This is where Kubernetes Network Policies come into play.</p>
<h2 id="heading-what-are-kubernetes-network-policies"><strong>What Are Kubernetes Network Policies?</strong></h2>
<p>Kubernetes Network Policies give you a way to enforce fine-grained control over the flow of traffic within your pods. These policies allow you to define which pods can communicate with each other or with other devices – so they act as a security layer with rules that restrict or allow specific types of traffic.</p>
<p>For example, if certain pods handle sensitive data or information, Network Policies can ensure that only authorized pods or external systems can gain access to it.</p>
<p>Implementing Network Policies also helps your Kubernetes clusters maintain security and compliance by restricting unnecessary communication and reducing traffic flow that could cause a security breach.</p>
<h3 id="heading-how-kubernetes-network-policies-work">How Kubernetes Network Policies Work</h3>
<p>Kubernetes Network Policies provide fine-grained access control within the Kubernetes cluster to manage network traffic at the pod level. Here, you can define separate rules for ingress and egress and restrict traffic to a particular port range.</p>
<p>In Kubernetes Network Policies, multiple policies can target the same pod. In this case, you can create "allow" rules to determine which traffic is permitted. Any traffic that doesn’t match the "allow" rule will be blocked.</p>
<p>Network Policies use IP addresses and port numbers to regulate traffic. This provides control over network flows to adhere to specific security requirements.</p>
<p>On the other hand, Network Policies aren’t a complete solution within an environment due to certain limitations. They cannot log blocked traffic events, meaning you cannot observe or debug why and when the Kubernetes Network Policy is blocking specific traffic. To achieve this, you need to use external tools supported by your CNI plugin.</p>
<p>CNI stands for Container Network Interface, a standard interface used by Kubernetes to manage network resources in containers. The CNI plugin is essential for providing container networking capabilities such as IP address allocation, routing, and enforcement of network policies. The plugin also enables the cluster to handle pod networking, including assigning network policies to pods and managing traffic flow between them.</p>
<p>Some popular network plugins include Calico, Cilium, Flannel, and Weave Net, each offering unique features and support for Network Policy integration.</p>
<h3 id="heading-how-to-implement-networking-policies"><strong>How to Implement Networking Policies</strong></h3>
<p>Properly implementing Network Policies relies on the CNI plugin you’re using in the Kubernetes Cluster. For Network Policies to take effect, the CNI plugin configured on your cluster must support them.</p>
<p>Network policies are usually enabled by default in managed Kubernetes services provided by cloud platforms such as Amazon EKS, Microsoft Azure AKS, or Google Cloud GKE. But if you manage your own cluster, you need to ensure that your CNI plugin is compatible. For example, the popular CNI plugin Flannel doesn’t support network policies, whereas Calico does.</p>
<h2 id="heading-how-to-set-up-a-simple-kubernetes-network-policy-on-eks"><strong>How to Set Up a Simple Kubernetes Network Policy on EKS</strong></h2>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Ensure you have the following installed on your Ubuntu Server:</p>
<ul>
<li><h4 id="heading-aws-cli-for-authentication-and-interactions-with-aws-resources"><code>AWS CLI</code>: For authentication and interactions with AWS resources</h4>
</li>
<li><h4 id="heading-kubectl-kubernetes-cli"><code>kubectl</code>: Kubernetes CLI</h4>
</li>
<li><h4 id="heading-eksctl-this-is-a-cli-for-managing-eks-clusters"><code>eksctl</code>: This is a CLI for managing EKS clusters</h4>
</li>
</ul>
<h3 id="heading-steps"><strong>Steps</strong></h3>
<p>First, create your AWS EKS cluster using the following CLI commands:</p>
<pre><code class="lang-bash">eksctl create cluster \

  --name my-eks-cluster \

  --region us-east-1 \

  --nodegroup-name ng-eks \

  --node-type t3.medium \

  --nodes 3 \

  --nodes-min 2 \

  --nodes-max 4 \

  --with-oidc \

  --version 1.31
</code></pre>
<p>Next, enable the Amazon VPC CNI plugin for your Kubernetes cluster. To do this, within your EKS Cluster, make sure that the Amazon VPC CNI plugin is installed to manage pod networking.  </p>
<p>Check the status like this:</p>
<pre><code class="lang-bash">kubectl get pods -n kube-system | grep aws-node
</code></pre>
<p>If it’s not running, deploy or update it to run on the cluster</p>
<pre><code class="lang-bash">kubectl apply -f https://raw.githubusercontent.com/aws/amazon-vpc-cni-k8s/master/config/v1.12/aws-k8s-cni.yaml
</code></pre>
<p>Amazon VPC CNI does not support and enforce network policies. So we have to install Calico, which is a CNI that works with the VPC CNI for Network Policies.</p>
<pre><code class="lang-bash">kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/calico.yaml
</code></pre>
<p>Confirm that Calico is installed and running like this:</p>
<pre><code class="lang-bash">kubectl get pods -n kube-system | grep calico
</code></pre>
<p>Now that we have set up Calico on our AWS EKS Cluster, let's examine various Kubernetes Network Policies that we can apply to it.</p>
<h3 id="heading-allow-all-traffic-to-a-specific-pod-in-the-cluster">Allow all traffic to a specific pod in the cluster:</h3>
<pre><code class="lang-bash">apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

  name: pod-network-policy

spec:

  podSelector:

    matchLabels:

      app: application-demo

  policyTypes:

    - Ingress

    - Egress

  ingress:

    - {}

  egress:

    - {}
</code></pre>
<p>This configuration defines a NetworkPolicy named <code>pod-network-policy</code> that applies to all pods with the label <code>app: application-demo</code>. The <code>podSelector</code> ensures that only the pods with this label are targeted.</p>
<ul>
<li><p>The <code>policyTypes</code> field indicates that this policy controls both <strong>Ingress</strong> (incoming traffic) and <strong>Egress</strong> (outgoing traffic).</p>
</li>
<li><p>The <code>ingress</code> and <code>egress</code> rules are defined with empty braces <code>{}</code>, meaning no restrictions are applied—all traffic is allowed, both inbound and outbound.</p>
</li>
</ul>
<h3 id="heading-deny-all-traffic-to-a-pod-in-the-cluster">Deny all traffic to a pod in the cluster:</h3>
<pre><code class="lang-bash">
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-network-policy
spec:
  podSelector:
    matchLabels:
      app: application-demo
  policyTypes:
    - Ingress
    - Egress
</code></pre>
<p>This configuration also selects pods labeled <code>app: application-demo</code> and applies the policy to both Ingress and Egress traffic.</p>
<p>Since no specific rules are defined, Kubernetes denies all traffic by default. This is also known as a "deny by default" policy, used to enforce strict isolation, preventing pods from communicating with others unless explicitly allowed by additional policies.</p>
<h3 id="heading-deny-all-ingress-traffic-to-the-pods-in-the-cluster">Deny all ingress traffic to the pods in the cluster</h3>
<pre><code class="lang-bash">apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-network-policy
spec:
  podSelector: {}
  policyTypes:
    - Ingress
</code></pre>
<p>This configuration applies a NetworkPolicy to all pods in the namespace.</p>
<ul>
<li><p>The empty <code>podSelector</code> is empty (<code>{}</code>), meaning it applies to all pods in the namespace, regardless of their labels.</p>
</li>
<li><p>The <code>policyTypes</code> field specifies that the policy only applies to Ingress traffic.</p>
</li>
<li><p>Since no explicit Ingress rules are defined, Kubernetes blocks all incoming traffic by default.</p>
</li>
</ul>
<h3 id="heading-deny-all-egress-traffic-to-the-pods-in-the-cluster">Deny all egress traffic to the pods in the cluster</h3>
<pre><code class="lang-bash">apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

  name: pod-network-policy

spec:

  podSelector: {}

  policyTypes:

    - Egress
</code></pre>
<p>In the configuration above:</p>
<ul>
<li><p>The <code>podSelector</code> is empty (<code>{}</code>), meaning the policy applies to all pods in the namespace.</p>
</li>
<li><p>The <code>policyTypes</code> field specifies that this policy only applies to Egress traffic.</p>
</li>
<li><p>Since no explicit Egress rules are defined, Kubernetes blocks all outgoing traffic for the target pods by default.</p>
</li>
</ul>
<h2 id="heading-when-and-why-to-use-kubernetes-network-policies">When and Why to Use Kubernetes Network Policies</h2>
<p>There are various use cases for implementing Kubernetes Network Policies to improve cluster security.</p>
<p>For example, perhaps you want to restrict who/what can access the database. If you have a database deployed within the cluster, Kubernetes Network Policies ensure that only authorized pods can communicate with it, blocking access from unauthorized applications within the cluster.</p>
<p>Or perhaps you want to isolate sensitive pods. Properly implementating Network Policies helps isolate sensitive pods that do not need to accept inbound traffic from other pods, strengthening security within the infrastructure.</p>
<h2 id="heading-best-practices-for-implementing-kubernetes-network-policies-on-your-cluster"><strong>Best Practices for Implementing Kubernetes Network Policies on Your Cluster</strong></h2>
<p>To maximize the effectiveness and security benefits of your Kubernetes Network Policies, keep these best practices in mind:</p>
<ul>
<li><p><strong>Ensure all pods are covered by a Network Policy:</strong> In an ideal production environment, all pods within the cluster should be covered by a network policy that limits their network to only the Ingress and Egress targets set in the configuration. Without this policy in place, all the pods can communicate freely, posing a huge security risk.</p>
</li>
<li><p><strong>Complement Network Policies with other security measures</strong>: While Network Policies are essential for Network isolation, they should be part of a wider security and networking strategy for your cluster. Additional safeguards should include Role-Based Access Control, which restricts unauthorized users from accessing or modifying the pod configurations, and advanced security contexts, which limit container capabilities.</p>
</li>
<li><p><strong>Always test Network Policies before deploying to production.</strong> Kubernetes Network Policies can be a bit of a hassle to validate, especially in a production environment, because they may hinder many running processes within the cluster. Always test new policies to ensure that they are working as intended within the cluster. For example, if you implement a new testing policy, use tools like curl or ping to verify blocked connectivity within the cluster.</p>
</li>
<li><p><strong>Always review your Network Policies as the cluster grows.</strong> As your cluster grows with the increasing user base and engineering needs, your Network Policies must always reflect new workloads, such as Pods and Namespaces. It is always best to review and update your Network Policies to stay relevant and ensure your environment is secure. </p>
</li>
<li><p><strong>Use precise target selectors for the configurations:</strong> Be specific when defining pod selectors, namespaces, and ipBlock ranges within your Network policies. For example, if you are working with namespace selectors, ensure that all the pods within that namespace conform to its security goals. Avoid using namespace selectors if you need to deploy pods that should communicate with other pods in the namespace. This is ideal because implementing namespace or pod selectors vaguely will impact the server, leading to unintended access.</p>
</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In this article, you learned about Kubernetes Network Policies as a way to manage and restrict communication between pods. Since pods don’t have network isolation by default, setting up the right policies is important for security.</p>
<p>While Network Policies play an important role, it is also important to protect your Cloud environment by ensuring your infrastructure is hardened – so make sure you also implement RBAC and regular vulnerability scans. You should also allocate only needed pod resources, build minimal base images for the pods, and follow Kubernetes security best practices in general.</p>
<p>By doing this, you can achieve end-to-end protection for your workloads.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Monitor Your Kubernetes Clusters with Prometheus and Grafana on AWS ]]>
                </title>
                <description>
                    <![CDATA[ Creating a solid application monitoring and observability strategy is a critical foundational step when deploying infrastructure or software in any environment. Monitoring ensures that your systems are running smoothly, while observability provides i... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/kubernetes-cluster-observability-with-prometheus-and-grafana-on-aws/</link>
                <guid isPermaLink="false">6790382dcb2eedbe449b6899</guid>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Grafana ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudnative ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Wed, 22 Jan 2025 00:13:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737504669572/017570c6-7676-44e1-aa19-4257dd7d30e7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Creating a solid application monitoring and observability strategy is a critical foundational step when deploying infrastructure or software in any environment. Monitoring ensures that your systems are running smoothly, while observability provides insights into the internal state of your application through the data generated. Together, they help you detect and address issues proactively rather than reacting after a failure occurs.</p>
<p>In Kubernetes environments, the complexity of managing distributed microservices can be challenging. For instance, an application usually spans multiple pods, nodes, and clusters. Because of Kubernetes’s dynamic nature, where pods are frequently created and terminated, proper monitoring and observability are ideal for capturing its fleeting behavior.</p>
<p>Imagine building a microservices application with several connected services handling critical components such as authentication, payments, and databases without proper monitoring. A sudden traffic spike could affect a single service, cascading to other services, causing the system to crash and resulting in downtime.</p>
<p>Without proper visibility, you may struggle to find the root cause of the issue. You may spend hours manually going through logs – and meanwhile, users are frustrated, and businesses are losing revenue and customer trust.</p>
<p>Before we begin the project, you’ll learn key monitoring and observability concepts, as well as why tools like Prometheus and Grafana are crucial for setting up a robust monitoring stack on your Kubernetes infrastructure.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-understanding-monitoring-and-observability">Understanding Monitoring and Observability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tools-for-monitoring-and-observability">Tools for Monitoring and Observability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-prometheus-and-grafana-on-aws-eks-using-helm">How to Deploy Prometheus and Grafana on AWS EKS using Helm</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-understanding-monitoring-and-observability">Understanding Monitoring and Observability</h2>
<p>Implementing a proper monitoring and observability approach is important in fast-paced production Kubernetes environments. This helps in situations where downtime can lead to serious business loss and damage to customer trust. It’ll hopefully help you avoid the dreaded 2 am calls that are usually triggered by alert noise so you can focus on adding more innovative features to your software (rather than spending so much energy firefighting).</p>
<p>Monitoring and observability are often referred to as the same thing. But they serve two different purposes, especially for development and engineering teams.</p>
<h3 id="heading-monitoring">Monitoring</h3>
<p>In the software development lifecycle, monitoring is the practice of analyzing data in real time or reviewing data trends to ensure the health and performance of systems, infrastructure, and applications. Monitoring acts as the eyes and ears of your IT operations, collecting insightful data and presenting it in a way that is actionable.</p>
<p>If you have visited the IT department of a well-established organization, you have most likely seen large screens displaying colorful dashboards with charts and real-time statistics. This offers a centralized view of the key metrics, such as the server uptime, application response times, and resource usage.</p>
<h3 id="heading-observability">Observability</h3>
<p>Observability helps to address issues that haven’t been anticipated. These are usually called “unknown unknowns."  Unlike monitoring, which deals with predefined parameters and data, observability goes deeper into the application to give a broader view.</p>
<p>This not only helps to answer what is happening within your system and why it is happening. It also uses patterns within the system and application operations to detect and resolve issues efficiently. </p>
<p>Observability revolves around the three pillars of data: <strong>metrics</strong>, <strong>logs</strong>, and <strong>traces</strong>.</p>
<h4 id="heading-1-metrics">1. Metrics</h4>
<p>Metrics consist of time-series measurements such as CPU usage and memory consumption. These data points help teams to manage, optimize, and predict system performance and deviations from expected behavior.</p>
<h4 id="heading-2-logs">2. Logs</h4>
<p>Logs serve as a history of what happened within the system. It is a trail for engineers, especially during troubleshooting. Logs are important in diagnosing root causes and discovering malicious activities.</p>
<h4 id="heading-3-traces">3. Traces</h4>
<p>Traces provide insights into application workflows by tracking requests as they move through various components. They are good for highlighting latency issues and potential points of failure.</p>
<h2 id="heading-tools-for-monitoring-and-observability">Tools for Monitoring and Observability</h2>
<p>Now that you understand the theory behind monitoring and observability, you may be wondering what platforms and tools are available to developers to collect data and get insights about their services.</p>
<p>In the world of cloud-native infrastructure and Kubernetes, many users gravitate towards the popular stack of Prometheus and Grafana.</p>
<h3 id="heading-prometheus">Prometheus</h3>
<p>Prometheus is an open-source tool that specializes in collecting metrics as time-series data. The information is stored with the timestamp when it was recorded.<br>The Prometheus ecosystem includes the main Prometheus server, which scrapes and stores time-series data, an alert manager for managing alerts, a push gateway for handling metrics from short-lived jobs, and exporters for collecting metrics from various services connected to the cluster.</p>
<p>It fits both in machine-centric and application-centric monitoring, especially for microservices in a Kubernetes cluster. It’s designed to be the system you go to if there is a system outage and you need to quickly diagnose problems.</p>
<p>The Prometheus ecosystem includes the main Prometheus server, which scrapes and stores time-series data, an alert manager for managing alerts, a push gateway for handling metrics from short-lived jobs, and exporters for collecting metrics from various services connected to the cluster.</p>
<p>Prometheus fits both in machine-centric and application-centric monitoring, especially for microservices in a Kubernetes cluster. It’s designed to be the system you go to if there is a system outage and you need to quickly diagnose problems.</p>
<h3 id="heading-grafana">Grafana</h3>
<p>Grafana is a visualization tool that transforms, queries, visualizes, and sets alerts on raw metrics stored in Prometheus. With Grafana, you can explore metrics and logs wherever they are stored and display the data on live dashboards. This allows teams to monitor system performance, identify trends, and act quickly on anomalies.</p>
<p>Prometheus and Grafana are compatible with containerized applications, especially in Kubernetes environments. It can also manage workloads outside Kubernetes for flexibility. They are both open-source tools that give developers control over the implementation. There is no licensing cost, which helps teams that cannot afford expensive, powerful solutions.</p>
<p>By combining Prometheus and Grafana, your team gets helpful insights into the system to optimize performance, track errors, and aid troubleshooting processes.</p>
<h2 id="heading-how-to-deploy-prometheus-and-grafana-on-aws-eks-using-helm">How to Deploy Prometheus and Grafana on AWS EKS using Helm</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>For this project, we will use an EC2 instance with the Ubuntu 22.04 operating system. If you are using Windows or a Mac, log into AWS to create your virtual machine.</p>
<p>Here’s what else you’ll need:</p>
<h4 id="heading-1-aws-account-setup-with-access-keys-and-secret-keys">1. AWS account setup with access keys and secret keys</h4>
<ul>
<li><p><a target="_blank" href="https://portal.aws.amazon.com/billing/signup">AWS Sign Up</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html">AWS Access ID and Secret Keys</a></p>
</li>
</ul>
<h4 id="heading-2-knowledge-of-kubernetes">2. Knowledge of Kubernetes</h4>
<ul>
<li><a target="_blank" href="https://kubernetes.io/docs/home/">Kubernetes Official Documentation</a></li>
</ul>
<h4 id="heading-3-aws-cli-installation-for-the-virtual-server">3. AWS CLI installation for the virtual server</h4>
<ul>
<li><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI Installation Guide</a></li>
</ul>
<h3 id="heading-getting-started">Getting Started</h3>
<p>Let’s start by setting up an EKS cluster on a virtual server and installing the required tools on the server. Then, we’ll deploy our monitoring tools, Prometheus and Grafana, using Helm charts. In the end, we’ll deploy an NGINX web application on Kubernetes and use Grafana to visualize the pod performance and cluster resource usage on the cluster.</p>
<h3 id="heading-step-1-install-aws-cli-eksctl-kubectl-and-helm">Step 1: Install AWS CLI, <code>eksctl</code>, <code>kubectl</code>, and Helm</h3>
<p>AWS CLI is a tool that allows users to interact with AWS services using the command-line interface. It makes the management of cloud resources simpler and enables admins to configure AWS services.</p>
<p>Here, we will install AWS CLI on our server to be able to create Kubernetes resources.</p>
<p>On your server, run the following commands:</p>
<pre><code class="lang-bash">curl <span class="hljs-string">"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"</span> -o <span class="hljs-string">"awscliv2.zip"</span>

sudo apt install unzip

unzip awscliv2.zip

sudo ./aws/install
</code></pre>
<p>Verify the installation by running this:</p>
<pre><code class="lang-bash">aws --version
</code></pre>
<p>After installation, configure the AWS CLI with your credentials using the following command:</p>
<pre><code class="lang-bash">aws configure
</code></pre>
<p>You will be prompted to enter your AWS Access Key ID, Secret Access Key, Default region name, and default output format.</p>
<p>Next, we need to install eksctl. <code>eksctl</code> is a command-line tool that simplifies the creation and management of Kubernetes clusters on AWS. It helps you configure, set, and maintain clusters and allows you to manage clusters more effectively.</p>
<p>This tool removes the complexities of setting up a production-grade cluster, helping you and your admins focus only on application development and deployment.</p>
<p>To set up <code>eksctl</code> on your machine, download the latest release using the following command:</p>
<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
</code></pre>
<p>Run <code>eksctl version</code> to confirm its successful installation and the version downloaded.</p>
<pre><code class="lang-bash">eksctl version <span class="hljs-comment"># 0.198.0</span>
</code></pre>
<p>Next, we’ll run Kubectl which is a command line interface for managing and interacting with Kubernetes clusters. It enables users to deploy and manage applications within a Kubernetes environment.</p>
<p>With Kubectl, you can perform various crucial operations such as scaling, deployments, inspecting cluster status, and managing networking.</p>
<p>To install <code>kubectl</code>, run the following commands:</p>
<pre><code class="lang-bash">curl -LO <span class="hljs-string">"https://storage.googleapis.com/kubernetes-release/release/<span class="hljs-subst">$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)</span>/bin/linux/amd64/kubectl"</span>
chmod +x ./kubectl
sudo mv ./kubectl /usr/<span class="hljs-built_in">local</span>/bin
</code></pre>
<p>Run <code>kubectl</code> on your command line to confirm it has been installed successfully:</p>
<pre><code class="lang-bash">kubectl version 
<span class="hljs-comment"># client version: 0.198.0</span>
<span class="hljs-comment"># Kustomize Versionv: 5.4.2</span>
<span class="hljs-comment"># Server Version: v1.30.7-eks-56e63d8</span>
</code></pre>
<p>Finally, we’ll install Helm which is a Kubernetes Package manager that simplifies the deployments and management of applications in Kubernetes. It uses <a target="_blank" href="https://helm.sh/docs/topics/charts/"><strong>charts</strong></a> to define Kubernetes resources into a collection of files, handles templating and versioning, and makes application deployment easier.</p>
<p>Here, we will install the Helm package manager on our virtual machine for our cluster deployments. This downloads the installation script and saves it in the <code>get_helm.sh</code> file.</p>
<p>Next, the file is set to executable, which allows only the user to run it. Finally, the script is executed using the <code>./get_helm.sh</code> command.</p>
<pre><code class="lang-bash">curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3

chmod 700 get_helm.sh

./get_helm.sh
</code></pre>
<h3 id="heading-step-2-create-a-kubernetes-cluster">Step 2: Create a Kubernetes Cluster</h3>
<p>Next, we need to create our Kubernetes cluster in AWS with the <code>eksctl</code> command line. We can do this with the following command:</p>
<pre><code class="lang-bash">eksctl create cluster --name my-prac-cluster-1 --version 1.30 --region us-east-1 --nodegroup-name worker-nodes --node-type t2.medium --nodes 2 --nodes-min 2 --nodes-max 3
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXf-np7NdxK-zwQLtYoVE51RUoHYMf5GBHT1tAEjV-eoxk2xCvH13s0tjzPdIb8QVt5amijSBjkpaAh4AuPQd4DJvtnKmMpPQ1_dFoRx1KRNRiCto0U7CpXU-rsd-KH8NhuQoHfRAg?key=U2Fi6zvcj43zRMwXR2oKCzLU" alt="Terminal window in Visual Studio Code displaying a series of commands and outputs related to setting up an EKS cluster. The log includes status updates, such as &quot;creating addon&quot; and &quot;EKS cluster resources have been created.&quot; The environment is Ubuntu, visible through the desktop interface and application icons on the left." width="1366" height="768" loading="lazy"></p>
<p>Let’s break down the command:</p>
<ul>
<li><p><code>–name my-prac-cluster-1</code>: This specifies the name of the EKS cluster that will be created. In this case, the cluster will be named <strong>my-prac-cluster-1</strong>.</p>
</li>
<li><p><code>–version 1.30</code>: This sets the Kubernetes version for the cluster. Here, the version will be version 1.30.</p>
</li>
<li><p><code>--region us-east-1</code>: This specifies the AWS region where the cluster will be provisioned on AWS. Here, it is set to us-east-1.</p>
</li>
<li><p><code>--nodegroup-name worker-nodes</code>: This defines the name of the node groups that will be created. In this case, it’s named <strong>worker-nodes</strong>.</p>
</li>
<li><p><code>--node-type t2.large</code>: This sets the instance type for the worker nodes in the <code>node-group</code>.</p>
</li>
<li><p><code>--nodes 2</code>: This sets the desired number of worker nodes in the node group.</p>
</li>
<li><p><code>--nodes-min 2</code>: This sets the minimum number of worker nodes that should be maintained in the node group to 2.</p>
</li>
<li><p><code>--nodes-max 3</code>: This defines the maximum number of worker nodes allowed in the node group and sets it to 3.</p>
</li>
</ul>
<p>Once the cluster comes up, run the command <code>kubectl get nodes</code> to ensure that the cluster is set up properly.</p>
<h3 id="heading-step-3-install-the-metrics-server">Step 3: Install the Metrics Server</h3>
<p>The metrics server is a component that collects resource data from the Kubelets on each node in the cluster. This includes metrics such as CPU, memory, and network usage, which Prometheus can access. The server provides a single source of truth for resource data and is easy to deploy and use.</p>
<p>Run the following script to install the metrics server:</p>
<pre><code class="lang-bash">kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
</code></pre>
<p>To verify the installation, run the following command:</p>
<pre><code class="lang-bash">kubectl get deployment metrics-server -n kube-system
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734913540206/47361106-21b0-4076-91c2-ffb8a925b372.jpeg" alt="A terminal screenshot displaying the command output for `kubectl get deployment metrics-server -n kube-system`. It shows the deployment details of &quot;metrics-server&quot; with readiness, up-to-date and available statuses as 1, and an age of 51 minutes." class="image--center mx-auto" width="1122" height="234" loading="lazy"></p>
<h3 id="heading-step-4-install-the-iam-oidc-identity-provider-and-amazon-ebs-csi-driver">Step 4: Install the IAM OIDC Identity Provider and Amazon EBS CSI Driver</h3>
<p>The IAM OpenID connect provider allows Kubernetes access to AWS resources within the cluster. Here, we need EBS volumes to create persistent storage for Prometheus pods.</p>
<p>Run the following commands to create the IAM OIDC provider:</p>
<pre><code class="lang-bash">eksctl utils associate-iam-oidc-provider --cluster my-prac-cluster-1 --approve
</code></pre>
<p>Next, we will create the Amazon EBS CSI Driver that will provide permissions for the cluster to access the EBS volumes. Replace the placeholder “my-cluster” with your cluster name.</p>
<pre><code class="lang-bash">eksctl create iamserviceaccount \

--name ebs-csi-controller-sa \

--namespace kube-system \

--cluster my-prac-cluster-1 \

--role-name AmazonEKS_EBS_CSI_DriverRole \

--role-only \

--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \

--approve
</code></pre>
<p>Now, we need to add the AWS EBS Driver Addon to the cluster using the following commands:</p>
<pre><code class="lang-bash">eksctl create addon --name aws-ebs-csi-driver --cluster &lt;cluster_name&gt; --service-account-role-arn arn:aws:iam::&lt;AWS_ACCOUNT_ID&gt;:role/AmazonEKS_EBS_CSI_DriverRole --force
</code></pre>
<p>Adding the AWS EBS CSI Driver to your Kubernetes cluster enables the cluster to dynamically create and manage EBS volumes for persistent storage within the cluster. Since our Prometheus installation needs persistent volumes, this add-on will enable the cluster to create EBS volumes to persist data.</p>
<p>Now, our future Prometheus installation will create EBS volumes for persistent storage.</p>
<h3 id="heading-step-5-install-prometheus-and-grafana">Step 5: Install Prometheus and Grafana.</h3>
<p>To install Prometheus and Grafana, we need to add the Helm Stable Charts for the local client.</p>
<p>Run the command below:</p>
<pre><code class="lang-bash">helm repo add stable https://charts.helm.sh/stable
</code></pre>
<p>Next, we will add the Prometheus Helm repo:</p>
<pre><code class="lang-bash">helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
</code></pre>
<p>We’ll use the Prometheus community version because it is well-maintained by the Prometheus community. It offers faster updates and continuous improvements for different Kubernetes environments.</p>
<p>Next, create the Prometheus namespace:</p>
<pre><code class="lang-bash">kubectl create namespace prometheus
</code></pre>
<p>Install Prometheus and Grafana through the <code>kube-prometheus-stack</code> Helm Chart:</p>
<pre><code class="lang-bash">helm install stable prometheus-community/kube-prometheus-stack -n prometheus
</code></pre>
<p>When that’s done, verify that the Prometheus deployment and service are installed by using the command below:</p>
<pre><code class="lang-bash">kubectl get all -n prometheus
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734913921541/9b851fcc-c390-4d86-9108-adaaddd58d07.jpeg" alt="Terminal output showing Kubernetes resources in the &quot;prometheus&quot; namespace. It lists several pods and services, each with information on readiness, status, restarts, age, type, cluster IP, and ports. All pods are in the &quot;Running&quot; status, with no restarts." class="image--center mx-auto" width="1135" height="597" loading="lazy"></p>
<p>At this stage, you should change the service type from a ClusterIP to a LoadBalancer in the manifest file. We can update the file by running the command below:</p>
<pre><code class="lang-bash">kubectl edit svc stable-kube-prometheus-sta-prometheus -n prometheus
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734914216836/217e2e5e-1d75-4ca4-88eb-56276f25c12f.jpeg" alt=" Screenshot of a Kubernetes service YAML file edited with kubectl for Prometheus in the prometheus namespace." class="image--center mx-auto" width="1172" height="584" loading="lazy"></p>
<p>After the update, a LoadBalancer URL will be generated for you to access your Prometheus Dashboard.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcWtK8G1cJTD8GI-vACe_KexsXIp5SEXYiGTuBXZNQJ93tpo-XrMVb6ekA1ZRDrAODyFn29p3JdQDOHCdMnq2eapX4drdLMJ_8u_B8z1Jl0LqJjIHJwwIbDhgRUU5tlkGhhnBdYKQ?key=U2Fi6zvcj43zRMwXR2oKCzLU" alt=" Prometheus dashboard showing the &quot;Targets&quot; page with active scrape pools, including details such as endpoints, state, labels, last scrape, scrape duration, and errors for Prometheus Alertmanager services." width="1366" height="768" loading="lazy"></p>
<p>Next, we’ll move over to Grafana. Change the SVC file of Grafana to create a LoadBalancer and expose it to the public using the command below:</p>
<pre><code class="lang-bash">kubectl edit svc stable-grafana -n prometheus
</code></pre>
<p>Next, we will update the Grafana SVC file by changing the service <code>type</code> from <code>ClusterIP</code> to <code>LoadBalancer</code> to expose it to the public using the command below:</p>
<pre><code class="lang-bash">kubectl edit svc stable-grafana -n prometheus
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734914435224/1ab76b93-2b35-4074-92cb-c7c3173edaee.jpeg" alt=" Screenshot of a Kubernetes service YAML configuration file edited using kubectl edit svc stable-grafana -n prometheus, displaying the details for the Grafana service in the prometheus namespace" class="image--center mx-auto" width="1144" height="601" loading="lazy"></p>
<p>Once the settings are saved, you can use the <code>LoadBalancer</code> link to access your Grafana Dashboard from the browser. The username is <strong>admin</strong>. To get the login password printed in the terminal, run the following command:</p>
<pre><code class="lang-bash">kubectl get secret --namespace prometheus stable-grafana -o jsonpath=<span class="hljs-string">"{.data.admin-password}"</span> | base64 --decode ; <span class="hljs-built_in">echo</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734915573534/12b64c7f-b74b-4ebb-8a1b-ae6c5357f76a.jpeg" alt="Grafana login screen displaying input fields for email/username and password with a notification indicating a successful login." class="image--center mx-auto" width="1276" height="629" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734915606224/bc06a48b-75e8-4115-816b-462c69975d97.jpeg" alt="Grafana welcome dashboard after a successful authentication" class="image--center mx-auto" width="1243" height="636" loading="lazy"></p>
<p>After successfully logging into the Grafana dashboard, the first step is to create a <code>datasource</code> that will provide the metrics for the Grafana visualization.</p>
<p>Go to <strong>Add your first data source</strong> and choose Prometheus as the Data Source.</p>
<p>Insert the Prometheus URL, and click on “<strong>Save and Test</strong>”. It should show success if Grafana queries the Prometheus URL successfully.</p>
<p>The next step is to create a Dashboard that our Grafana visualization will use to view the metrics of our pods. To do so, click on “<strong>Dashboards</strong>” and then on “<strong>Add Visualization.</strong>”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734915769650/10f7877b-7183-4709-b672-ff6d67ef529d.jpeg" alt="Screenshot of a configuration interface showing options for custom query parameters and HTTP method (set to POST) for Prometheus data source. Confirmation message states, &quot;Successfully queried the Prometheus API,&quot; with options to delete or save &amp; test." class="image--center mx-auto" width="1293" height="627" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734915836669/3fb27ce9-23d9-4c35-abf6-7ce44508720d.jpeg" alt="Grafana Dashboard interface with options to add a visualization, import a panel, or import a dashboard. There is a prominent button for adding a visualization." class="image--center mx-auto" width="1263" height="622" loading="lazy"></p>
<p>You’d be taken to an environment where you’d be required to import a dashboard. Select the data source as “<strong>Prometheus-1</strong>” and use the code “<strong>15760</strong>” to import the Node Exporter dashboard to view our pods.</p>
<p>Click on Load after importing the dashboard, and you will see your newly created dashboard.</p>
<p>Here, we can see the entire data of the cluster, the CPU and RAM use, and data regarding pods in a specified namespace.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734917376090/9e0097f4-07bb-495a-9572-060db373334c.jpeg" alt="Screenshot of a Grafana interface showing a &quot;Select data source&quot; window. Two data sources named &quot;Prometheus&quot; and &quot;prometheus-1&quot; are listed. Options for using mixed data sources, dashboards, and Grafana mock data are on the right." class="image--center mx-auto" width="1266" height="579" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734917392381/c7e01350-d90e-43a1-b172-82899f377d82.jpeg" alt="Screenshot of Grafana's &quot;Import dashboard&quot; page, showing options to upload a JSON file or enter a dashboard ID. The ID &quot;15760&quot; is entered in the input box, and there is a JSON model example displayed below." class="image--center mx-auto" width="1284" height="637" loading="lazy"></p>
<h3 id="heading-step-6-deploying-an-application-on-kubernetes-to-monitor-on-grafana">Step 6: Deploying an Application on Kubernetes to Monitor on Grafana.</h3>
<p>Finally, we will deploy an NGINX container in our EKS Cluster to monitor using Grafana. We need to create a Yaml deployment and service file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>

<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>

<span class="hljs-attr">metadata:</span>

<span class="hljs-attr">name:</span> <span class="hljs-string">nginx-app</span>

<span class="hljs-attr">spec:</span>

<span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>

<span class="hljs-attr">selector:</span>

<span class="hljs-attr">matchLabels:</span>

<span class="hljs-attr">app:</span> <span class="hljs-string">nginx-app</span>

<span class="hljs-attr">template:</span>

<span class="hljs-attr">metadata:</span>

<span class="hljs-attr">labels:</span>

<span class="hljs-attr">app:</span> <span class="hljs-string">nginx-app</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">nginx-app</span>

<span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>

<span class="hljs-attr">ports:</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>

<span class="hljs-meta">---</span>

<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>

<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>

<span class="hljs-attr">metadata:</span>

<span class="hljs-attr">name:</span> <span class="hljs-string">nginx-app</span>

<span class="hljs-attr">spec:</span>

<span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>

<span class="hljs-attr">ports:</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>

<span class="hljs-attr">targetPort:</span> <span class="hljs-number">80</span>

<span class="hljs-attr">selector:</span>

<span class="hljs-attr">app:</span> <span class="hljs-string">nginx-app</span>
</code></pre>
<p>To deploy the Node.js application on the Kubernetes cluster, use the following <code>kubectl</code> command. Verify the deployment by running the following <code>kubectl</code> command:</p>
<pre><code class="lang-bash">kubectl apply -f deployment.yml

kubectl get deployment

kubectl get pods
</code></pre>
<p>Click the load balancer URL to see your application on your browser:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734917018333/987b78c4-cae0-42dc-bd19-0a4f8a6a6b10.jpeg" alt="Browser window displaying the default welcome page for Nginx, indicating successful installation and suggesting further configuration." class="image--center mx-auto" width="1162" height="710" loading="lazy"></p>
<p>Let’s refresh our Grafana dashboard to see our NGINX web application in Grafana.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734917096760/72815d56-04a5-47cf-8b2b-89164ccb7e7a.jpeg" alt="A Kubernetes dashboard showing CPU and memory usage by container. The CPU usage graph is on the left, and the memory usage graph is on the right. Both graphs display data for containers named &quot;nginx-app&quot; over the last 5 minutes." class="image--center mx-auto" width="1278" height="626" loading="lazy"></p>
<h3 id="heading-step-7-deleting-the-cluster">Step 7: Deleting the Cluster</h3>
<p>Now that everything is set up, we can delete our Kubernetes Cluster to avoid extra costs. Run the following commands to do so:</p>
<pre><code class="lang-bash">eksctl delete cluster my-prac-cluster-1 –region us-east-1
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734917455255/04b00a79-d963-4e28-bc99-4cb6ea8c65cf.jpeg" alt="A terminal window displaying a series of commands and system messages related to the deletion of an EKS cluster and associated resources. It shows timestamps for each action, status updates, and confirmation that all cluster resources were deleted successfully." class="image--center mx-auto" width="1192" height="657" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article teaches the theory behind monitoring and observability and highlights the roles of Prometheus and Grafana in these processes.</p>
<p>We went through a hands-on deployment of Prometheus and Grafana on an EKS cluster and a web application to illustrate how they can be effectively monitored using Grafana.</p>
<p>By leveraging these tools, administrators can enjoy real-time visibility into their Kubernetes infrastructure, easily spot performance bottlenecks, and confidently make decisions that enhance application performance and reliability.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create and Manage Virtual Machines with the Vagrant Command Line Tool ]]>
                </title>
                <description>
                    <![CDATA[ Creating and managing virtual machines used to be a tedious and time-consuming process. Replicating the VM on a different server can also be challenging, and it gets harder if you have to replicate multiple VMs. But then Vagrant came along, a command... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-and-manage-virtual-machines-with-vagrant/</link>
                <guid isPermaLink="false">66d45f31246e57ac83a2c76b</guid>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ virtual machine ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Mon, 03 Apr 2023 20:15:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/Screenshot-2023-04-01-at-23.42.01-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Creating and managing virtual machines used to be a tedious and time-consuming process. Replicating the VM on a different server can also be challenging, and it gets harder if you have to replicate multiple VMs.</p>
<p>But then Vagrant came along, a command-line or shell tool that generally works with <a target="_blank" href="https://en.wikipedia.org/wiki/Hypervisor#:~:text=Type%2D2%20or%20hosted%20hypervisors">Type 2 hypervisors</a>. You use it to create and manage virtual machines. It is a powerful tool that can help simplify the setup and management of your development environment.</p>
<p>Vagrant can be really helpful if you work on a team or with multiple people. This is because it guarantees consistency in your development environment by ensuring that everyone utilizes the same environment, preventing compatibility issues.</p>
<p>This tutorial will guide you through the process of setting up a single Ubuntu Linux virtual machine with Vagrant and configuring a web server inside it.</p>
<h3 id="heading-prerequisites-for-this-tutorial-include">Prerequisites for this tutorial include:</h3>
<ul>
<li><p>A computer with at least 8GB of RAM</p>
</li>
<li><p>Basic knowledge of the Linux operating system</p>
</li>
</ul>
<h3 id="heading-required-tools-and-installation">Required Tools and Installation</h3>
<ul>
<li><p><strong>Oracle VirtualBox:</strong> Go to the <a target="_blank" href="https://www.virtualbox.org/wiki/Downloads">Oracle VirtualBox</a> website, find the version of VirtualBox that is compatible with your operating system, and follow the instructions to download and install it. Virtual Box will provide the virtual environment, while Vagrant will set it up and manage it.</p>
</li>
<li><p><strong>Vagrant:</strong> Visit the <a target="_blank" href="https://www.vagrantup.com/">Vagrant website</a> and follow the instructions to download and install the binary that is suitable for your operating system. In this tutorial, we'll be utilizing the open-source Vagrant binary.</p>
</li>
</ul>
<p>To check if the installation was successful, launch your preferred command line tool and enter the following command to output the installed version number:</p>
<pre><code class="lang-bash">$ vagrant --version
</code></pre>
<h2 id="heading-how-to-create-a-development-environment-with-vagrant">How to Create a Development Environment with Vagrant</h2>
<p>To create a Vagrant project, start by creating a new project directory in your preferred location for Vagrant configuration and related files.</p>
<pre><code class="lang-bash">$ mkdir vagrant-project &amp;&amp; <span class="hljs-built_in">cd</span> vagrant-project
</code></pre>
<p>Within this directory, create a new Vagrantfile. Vagrant uses the configuration in the Vagrantfile to build the VM. By default, Vagrant syncs the project directory where the Vagrantfile is initialized to /vagrant. This eliminates the need to worry about volumes for persisting data.</p>
<p>Vagrant uses the concept of boxes. Boxes are a complete base image of an operating system. The public <a target="_blank" href="https://app.vagrantup.com/boxes/search">vagrant box repository</a> contains a list of possible boxes. Choosing a box that matches the operating system used in your production environment is good practice.</p>
<p>A Vagrant box has the name of the user or organization that created it and the box name <code>user/boxname</code>. To initialize the Vagrant configuration file with an Ubuntu box, run the command:</p>
<pre><code class="lang-bash">$ vagrant init ubuntu/trusty64
</code></pre>
<p>This generates a Vagrantfile with a Ubuntu/trusty64 box in the current directory. The Vagrantfile, which is written in Ruby, contains the kind of VM to be used and various additional commented options such as network, port forwarding, disc capacity, and so on to assist in configuring the development environment.</p>
<p>You can add the <code>--minimal</code> flag to the initialization command of the Vagrantfile to generate a Vagrantfile without any additional settings.</p>
<p>Open the Vagrantfile with any editor of your choice. I will use the Vim editor in this tutorial.</p>
<pre><code class="lang-bash"> $ vim Vagrantfile
</code></pre>
<p>Removing the informational comments and some advanced configurations will leave the file like this:</p>
<pre><code class="lang-ruby"><span class="hljs-comment"># -*- mode: ruby -*-</span>
<span class="hljs-comment"># vi: set ft=ruby :</span>

Vagrant.configure(<span class="hljs-string">"2"</span>) <span class="hljs-keyword">do</span> <span class="hljs-params">|config|</span>
  config.vm.box = <span class="hljs-string">"ubuntu/xenial64"</span>
    config.vm.network <span class="hljs-string">"forwarded_port"</span>, <span class="hljs-symbol">guest:</span> <span class="hljs-number">8000</span>, <span class="hljs-symbol">host:</span> <span class="hljs-number">8000</span>
    config.vm.provider <span class="hljs-string">"virtualbox"</span> <span class="hljs-keyword">do</span> <span class="hljs-params">|vb|</span> vb.memory = <span class="hljs-string">"1024"</span>
 <span class="hljs-keyword">end</span>
  config.vm.provision <span class="hljs-symbol">:shell</span>, <span class="hljs-symbol">path:</span> <span class="hljs-string">"simple-node-project.sh"</span>, <span class="hljs-symbol">privileged:</span> <span class="hljs-literal">false</span>
<span class="hljs-keyword">end</span>
</code></pre>
<p>The <code>simple-node-project.sh</code> is a bash script that installs Node.js and Git, clones a project that creates a simple Node.js web server, and starts the server.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

 sudo apt-get update -y

 <span class="hljs-comment">## Git ##</span>
 <span class="hljs-built_in">echo</span> <span class="hljs-string">'###Installing Git..'</span>
 sudo apt-get install git -y

 git <span class="hljs-built_in">clone</span> https://github.com/Aijeyomah/simple-node-app.git

<span class="hljs-comment"># Installing latest Node and npm version</span>
 sudo apt-get install -y curl software-properties-common

<span class="hljs-comment"># Add Node.js PPA</span>
curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -

<span class="hljs-comment"># Install Node.js and npm</span>
sudo apt-get install -y nodejs

<span class="hljs-comment"># Verify installation</span>
node -v
npm -v

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Node.js has been installed successfully."</span>

<span class="hljs-comment"># navigate to app directory and start app</span>
<span class="hljs-built_in">cd</span> simple-node-app
node index.js &amp;
</code></pre>
<p>This Vagrant configuration sets up the following:</p>
<ul>
<li><p><code>ubuntu/trusty64</code> is specified as the virtual box base image</p>
</li>
<li><p>Forwards port 8000 of the VM to port 8000 of the host machine.</p>
</li>
<li><p>Allocates 1GB of memory to the VM</p>
</li>
<li><p>Runs <code>simple-node-project</code> to provision the VM</p>
</li>
<li><p>For the shell provisioner to run the script as a non-root user in a login shell, <code>privileged</code> is set to <code>false</code></p>
</li>
</ul>
<p>Save the <code>Vagrantfile</code> and start the virtual machine by running the following command:</p>
<pre><code class="lang-bash">$ vagrant up
</code></pre>
<p>The first time this command is run, it will download the latest version of the specified box, and it will configure and start the VM. This process might take some time, but when the Ubuntu box exists in the local machine the VM will start immediately.</p>
<p>Once the VM is running, you can access the web page by opening a web browser and navigating to <a target="_blank" href="http://localhost:8080"><code>http://localhost:8000</code></a>. You should see the <code>Hello World</code> message page if everything was set up correctly.</p>
<h2 id="heading-how-to-manage-vagrant">How to Manage Vagrant</h2>
<p>You can use Vagrant to manage the running virtual machine. Here are some useful Vagrant commands:</p>
<p><code>vagrant up</code>: Launches the virtual machine and provisions it according to the settings in the Vagrantfile. This command will simply connect to the virtual machine if it is already running.</p>
<p><code>vagrant halt</code>: Stops the virtual machine by delivering a shutdown signal to the guest operating system. This command is similar to shutting down a real computer.</p>
<p><code>vagrant reload</code>: Restarts the virtual machine and re-provisions it depending on any changes in the Vagrantfile.</p>
<p><code>vagrant ssh</code>: Connects to the virtual machine via SSH. This command is useful for accessing the command line interface of the virtual machine.</p>
<p><code>vagrant status</code>: Shows the current status of the virtual machine, including whether it's running, stopped, or suspended.</p>
<p><code>vagrant destroy</code>: Deletes the virtual machine and all associated resources. This command is useful for cleaning up your development environment.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we have learned how to utilize Vagrant to set up a reproducible and consistent development environment.</p>
<p>Using Vagrant can help you set up a virtual development environment that closely mimics your production environment. This allows you to test and develop your code in a consistent and isolated environment.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
