<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kalio Princewill</title>
    <description>The latest articles on DEV Community by Kalio Princewill (@kalio).</description>
    <link>https://dev.to/kalio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F832781%2F87a76295-6c09-4d45-a359-80c8e9a31e75.jpg</url>
      <title>DEV Community: Kalio Princewill</title>
      <link>https://dev.to/kalio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kalio"/>
    <language>en</language>
    <item>
      <title>How AI Engineers Actually Use Datasets: Test Cases, Edge Cases and Agent Reliability</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Mon, 06 Apr 2026 23:32:46 +0000</pubDate>
      <link>https://dev.to/kalio/how-ai-engineers-actually-use-datasets-test-cases-edge-cases-35gf</link>
      <guid>https://dev.to/kalio/how-ai-engineers-actually-use-datasets-test-cases-edge-cases-35gf</guid>
      <description>&lt;p&gt;Most AI agent discussions focus on models. In practice, the model is rarely the problem.&lt;/p&gt;

&lt;p&gt;When you build an agent today you are almost certainly not training it. The model is fixed. What determines whether the agent actually works is everything around it: the tools it can call, the prompts that guide it, the logic that decides what it does next.&lt;/p&gt;

&lt;p&gt;So when people say "we need more data," they usually do not mean training. They mean better test cases, clearer failure scenarios, and a way to measure whether the agent is behaving correctly.&lt;/p&gt;

&lt;p&gt;This article breaks down how to evaluate an AI agent properly: what to test, how to structure realistic scenarios from real world data, how to score the path the agent takes not just the answer it lands on, and how to design adversarial tests that force actual reasoning instead of pattern matching.&lt;/p&gt;

&lt;p&gt;Using SRE agents as the concrete example throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Are Not Doing vs What You Are
&lt;/h2&gt;

&lt;p&gt;Before anything else, this distinction matters.&lt;/p&gt;

&lt;p&gt;What you are NOT doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feeding logs into the model to teach it new things&lt;/li&gt;
&lt;li&gt;Fine tuning weights&lt;/li&gt;
&lt;li&gt;Changing how the underlying LLM reasons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you ARE doing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using real world logs to construct realistic test scenarios&lt;/li&gt;
&lt;li&gt;Grading whether the agent investigates correctly&lt;/li&gt;
&lt;li&gt;Exposing edge cases the agent currently fails at&lt;/li&gt;
&lt;li&gt;Using those failures to improve prompts, tools, and agent logic&lt;/li&gt;
&lt;li&gt;Building a test suite that gets harder as the agent gets better&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model does not improve through this process. What improves is the system around it. Test cases are how you measure that system rigorously instead of guessing.&lt;br&gt;
With that clear, the next question is what you are actually grading.&lt;/p&gt;
&lt;h2&gt;
  
  
  What You Are Actually Testing
&lt;/h2&gt;

&lt;p&gt;When you test an AI agent you are not checking if the model knows things. The model already knows things. You are checking three specific behaviours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the agent pick the right tools in the right order?&lt;/strong&gt;&lt;br&gt;
Given a scenario, does it investigate correctly or does it jump straight to conclusions?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it stop at the right time?&lt;/strong&gt;&lt;br&gt;
Does it know when it has found the root cause and stop, or does it keep going in circles?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can it reason through noise?&lt;/strong&gt;&lt;br&gt;
If there are red herrings in the data, metrics that look suspicious but are not causal, does it get distracted or stay on the right path?&lt;/p&gt;

&lt;p&gt;These are behaviours you grade. Not things you train. And the clearest way to see them in practice is to look at a real agent being built against exactly these constraints.&lt;/p&gt;
&lt;h2&gt;
  
  
  The SRE Agent As A Case Study
&lt;/h2&gt;

&lt;p&gt;An SRE (Site Reliability Engineering) agent is one that investigates production incidents automatically: it gets an alert, pulls logs and metrics, reasons across the signals, and produces a root cause report.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Tracer-Cloud/opensre/tree/main/tests" rel="noopener noreferrer"&gt;OpenSRE project&lt;/a&gt; is a good concrete example of this in practice. Their test suite lives in &lt;code&gt;tests/e2e/&lt;/code&gt; and covers Kubernetes and RDS Postgres scenarios. They are building a suite of realistic incident scenarios and checking whether the agent handles them correctly.&lt;/p&gt;

&lt;p&gt;You can run the agent directly against a test fixture like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;opensre investigate &lt;span class="nt"&gt;-i&lt;/span&gt; tests/e2e/kubernetes/fixtures/datadog_k8s_alert.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That JSON fixture is a synthetic but realistic alert, constructed to represent a specific failure mode with logs, metrics, and context included. The agent runs against it and you check whether the investigation was correct. That is the entire idea. Now let us look at what that fixture actually contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What A Test Case Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;A test case has four parts: the input the agent sees, the steps you expect it to take, the answer you expect it to reach, and the red herrings it should notice but not chase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TEST_CASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scenario&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RDS Postgres connection pool exhaustion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            2024-01-15 14:23:01 UTC [FATAL] remaining connection slots reserved for
            non-replication superuser connections
            2024-01-15 14:23:01 UTC [ERROR] connection to server failed: FATAL:
            sorry, too many clients already
            2024-01-15 14:23:04 UTC [WARNING] pool wait time exceeded 10000ms
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_connections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;498&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_connections_max&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_percent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# elevated but not the cause
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory_percent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;      &lt;span class="c1"&gt;# fine
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Database latency spike - P95 latency exceeded 4000ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_db_connections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_active_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_pool_configuration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommend_pool_size_increase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connection pool exhaustion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red_herrings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_percent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elevated but stable, not the cause of latency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;should_stop_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_pool_configuration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs and metrics are what the agent sees. The expected steps define the correct investigation path. The red herrings flag what the agent should notice but not chase. The stop condition catches agents that keep digging after the answer is already clear.&lt;/p&gt;

&lt;p&gt;The agent runs against this input and you grade whether it got the right root cause, investigated in the right order, and did not get pulled off track by the CPU metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trajectory Scoring
&lt;/h2&gt;

&lt;p&gt;Once you have test cases, you need a way to score them. Getting the right answer is not enough. You want to know if the agent got there the right way.&lt;/p&gt;

&lt;p&gt;This matters in practice because an agent that stumbles onto the correct answer after checking ten irrelevant things is not a reliable agent. It got lucky. Trajectory scoring measures the investigation path, not just the conclusion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_trajectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;penalties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual_steps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;expected_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;actual_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
            &lt;span class="c1"&gt;# penalise for investigating out of order
&lt;/span&gt;            &lt;span class="n"&gt;position_penalty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_position&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;actual_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;position_penalty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;penalties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unexpected step taken: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# penalise for not stopping when root cause was found
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual_steps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;penalties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent continued investigating after root cause was clear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;penalties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;penalties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;passed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# example usage
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;investigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_CASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;score_trajectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;actual_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;steps_taken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TEST_CASE&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# {"score": 0.85, "max_score": 1.0, "penalties": [], "passed": True}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function takes two lists: the steps the agent actually took, and the steps you expected it to take.&lt;br&gt;
For each step the agent took, it checks two things: was this step in the expected list at all, and if so, did it happen at roughly the right point in the investigation? If the agent checked check_db_connections first and that was expected first, full credit. If it checked it third when it should have been first, it gets penalised proportionally and scores. After scoring the steps, it checks whether the agent kept going past the point where it should have stopped.&lt;/p&gt;

&lt;p&gt;Trajectory scoring handles well-labelled scenarios where you know the expected path. But what happens when the scenario is deliberately designed to mislead?&lt;/p&gt;
&lt;h2&gt;
  
  
  Adversarial Tests: Forcing The Agent To Reason, Not Pattern Match
&lt;/h2&gt;

&lt;p&gt;Standard test cases check whether the agent handles known scenarios correctly. Adversarial tests go further. They check whether the agent actually reasons or just pattern matches.&lt;br&gt;
The difference matters because production incidents do not arrive cleanly. They arrive with noise, misleading signals, and symptoms that point in the wrong direction. An agent that pattern matches will chase the loudest signal. An agent that reasons will trace the causal chain.&lt;br&gt;
Adversarial tests deliberately inject red herrings to expose which one you have built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ADVERSARIAL_TEST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scenario&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kubernetes OOMKilled - misleading CPU spike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            2024-01-15 09:15:22 UTC [WARNING] Container memory usage at 94%
            2024-01-15 09:15:45 UTC [ERROR] OOMKilled: container exceeded memory limit
            2024-01-15 09:15:45 UTC [INFO] Pod restarting...
            2024-01-15 09:16:01 UTC [WARNING] CPU throttling detected on node
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_throttling_percent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# looks alarming, is a red herring
&lt;/span&gt;            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory_usage_percent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory_limit_mb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pod_restarts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High CPU throttling detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;container OOMKilled due to insufficient memory limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red_herring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu_throttling looks like the main issue but is a downstream symptom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_pod_events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_memory_usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_memory_limits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommend_memory_limit_increase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# agent should NOT go down this path
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incorrect_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_cpu_throttling&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommend_cpu_limit_increase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alert fires on CPU throttling. A pattern-matching agent sees 78% throttling and immediately recommends a CPU limit increase. That is wrong. The CPU throttling is a downstream symptom of the OOMKill restart loop. The real problem is the memory limit being too low. An agent that reasons traces from the OOMKill event back to the memory configuration and stops there.&lt;br&gt;
Now you understand what a test case is, how to score it, and how to stress test the agent against misleading signals. Before you start writing your own, it is worth looking at how others have structured theirs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Looking At Existing Test Suites Before Building Your Own
&lt;/h2&gt;

&lt;p&gt;Before writing test cases from scratch, you have to look at what others have already built. The structure is often more instructive than the content itself.&lt;br&gt;
The OpenSRE test suite separates scenarios by domain with fixture files containing realistic alert payloads. Reading those fixtures before writing your own will save you several wrong turns on what a well-structured test case should actually contain, what fields matter, how much context to include, and how to frame the expected behaviour clearly enough to grade against.&lt;br&gt;
Two other eval suites worth studying for the structural pattern regardless of your domain:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.swebench.com/SWE-bench/guides/datasets/" rel="noopener noreferrer"&gt;SWE-bench&lt;/a&gt;&lt;/strong&gt;: how Princeton structured software engineering task evals for coding agents. The input, expected output, graded result pattern maps directly to any agent domain.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/THUDM/AgentBench" rel="noopener noreferrer"&gt;AgentBench&lt;/a&gt;&lt;/strong&gt;: benchmark for LLM agents across different environments including OS tasks, database interactions, and web browsing. Useful for seeing how grading works across different action spaces and how to think about pass criteria when the action space is open-ended.&lt;/p&gt;

&lt;p&gt;The pattern across all of them is the same: realistic input, defined expected behaviour, graded output. Once you have that pattern clear in your head, the fastest way to build your own scenarios is synthetic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Synthetic Data: What It Is and When It Plateaus
&lt;/h2&gt;

&lt;p&gt;Synthetic test cases are ones you construct yourself. You write the logs, set the metrics, define the expected answer. You control everything.&lt;br&gt;
This is the right place to start. It is fast, you can cover specific failure modes methodically, and you can design adversarial cases precisely because you decide what the red herrings are.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_synthetic_rds_scenario&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;templates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slow_query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration: 45231 ms statement: SELECT * FROM orders WHERE status=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;autovacuum: found 80000 dead row versions in table orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_connections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red_herrings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db_connections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;normal range, not the cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing index causing full table scan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_slow_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_query_plans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommend_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replication_lag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;logs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replication slot lag: 8GB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WAL sender process waiting for WAL to be archived&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replication_lag_bytes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8589934592&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disk_io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red_herrings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low, not relevant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replication lag due to WAL accumulation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_replication_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_wal_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_replica_health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The limitation is that synthetic data plateaus. As you add more scenarios the agent improves, but the gains flatten over time. The reason is structural: every scenario you write comes from your own mental model of what can go wrong. You can only write what you can imagine, which means every edge case your synthetic suite does not cover is an edge case your agent has never been tested against.&lt;/p&gt;

&lt;p&gt;When gains plateau you have two options: build a different synthetic generator that introduces genuinely new failure patterns, or bring in real world data. Real world cases expose failure modes you never thought to write because they actually happened to someone. That is where the next section comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where To Get Real World Data For Test Cases
&lt;/h2&gt;

&lt;p&gt;To be clear again: you are not feeding these datasets into the model. You are reading them, understanding the failure patterns, and constructing test cases that reflect what actually happens in production.&lt;br&gt;
The SRE agent is the example we have been using throughout, but the same approach applies to any domain. If you are building an agent that handles customer support tickets, database query optimisation, fraud detection, or any other domain with structured inputs and measurable outcomes, the same process applies: find a labeled dataset in your domain, understand the failure patterns, and turn them into test cases. Here is a list of good dataset sites:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasetsearch.research.google.com/" rel="noopener noreferrer"&gt;Google Dataset Search&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
A search engine specifically for datasets. For your domains, search what your agent handles: "customer support tickets", "financial transactions", "medical records."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.kaggle.com/datasets" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
A large public dataset repository with a lot of labeled data across many domains. It covers finance, healthcare, e-commerce, NLP, and more. Many Kaggle datasets include notebooks showing how others have analysed them, which makes it easier to understand failure patterns before writing test cases.&lt;/p&gt;

&lt;p&gt;Once you find a dataset, you load and filter it to find the failure windows, the rows where something actually went wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# load a labeled anomaly dataset
&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server_machine_dataset.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# filter for labeled failure windows
&lt;/span&gt;&lt;span class="n"&gt;failure_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# the metrics leading up to the failure become your test case input
# the label tells you when the failure occurred
# you construct the expected root cause from the dataset documentation
&lt;/span&gt;
&lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scenario&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server anomaly from SMD dataset machine-1-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metrics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;failure_window&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;network saturation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# from dataset docs
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_network_throughput&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;check_active_connections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identify_traffic_source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing worth noting: the expected root cause is not something you derive from the data itself. It comes from the dataset documentation, which for published research datasets will describe what each labeled failure actually was. That documentation is the ground truth your test case is built on. Without it you have inputs but no correct answers to grade against, which means you have data but not a test suite.&lt;br&gt;
The goal across all of this is not to teach the agent. It is to know, with confidence, whether the agent you have built is reliable enough to trust in production.&lt;/p&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Implementing Automated Rules-Based Evaluations for LLM Applications</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Thu, 05 Feb 2026 13:16:46 +0000</pubDate>
      <link>https://dev.to/kalio/implementing-automated-rules-based-evaluations-for-llm-applications-468j</link>
      <guid>https://dev.to/kalio/implementing-automated-rules-based-evaluations-for-llm-applications-468j</guid>
      <description>&lt;p&gt;Building software with large language models (LLM) introduces a testing problem that traditional approaches cannot solve. When a function can return different yet equally valid outputs on each invocation, how do you know if it's working correctly?&lt;/p&gt;

&lt;p&gt;The standard answer in traditional software is simple: write tests that assert exact outputs. But LLMs are probabilistic systems. Ask them the same question twice and you might get two different phrasings, both correct. Ask them an ambiguous question and you might get a plausible-sounding answer that's completely fabricated (hallucination).&lt;br&gt;&lt;br&gt;
This creates tension. On one hand, LLMs enable applications that would be impossible to build with deterministic code. On the other hand, they behave in ways that make them difficult to test, debug, and deploy with confidence.&lt;/p&gt;

&lt;p&gt;This article explores a practical solution: rules-based evaluations integrated into continuous integration pipelines. We will demonstrate this through an AI-powered quiz generator, but the patterns apply to any LLM system where outputs must stay grounded in a specific context.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Testing LLM Applications Is Fundamentally Different
&lt;/h2&gt;

&lt;p&gt;Traditional software testing relies on predictability: input X, expect output Y, and assert equality. The test either passes or fails. &lt;/p&gt;

&lt;p&gt;LLM applications operate differently. The same prompt can produce multiple valid responses. A request for a quiz about science might correctly return questions about telescopes, physics, or Leonardo da Vinci's inventions depending on what the model selects from the available context.&lt;/p&gt;

&lt;p&gt;This probabilistic behavior isn't a bug. But it introduces risks that don't exist in deterministic systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination&lt;/strong&gt;: The model generates information that sounds plausible but isn't grounded in the provided context. Ask for a quiz about Rome when your dataset only covers Paris, and the model might confidently generate Roman history questions using its pre-training knowledge.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias and toxicity:&lt;/strong&gt; Without constraints, LLMs can produce outputs that reflect societal biases present in their training data or generate harmful content.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context drift&lt;/strong&gt;: Even when the model initially behaves correctly, changes to prompts, data, or model versions can introduce regressions that traditional tests won't catch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These failure modes mean you can't simply write &lt;code&gt;assert output == expected_output&lt;/code&gt;. You need evaluation strategies that account for variability while still enforcing correctness.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Are Evals?
&lt;/h2&gt;

&lt;p&gt;"Evals" are systematic methods for assessing model behavior. While academic benchmarks like MMLU measure general intelligence, they tell you nothing about whether your specific application will hallucinate. A model that scores well on a reading comprehension benchmark might still hallucinate answers when integrated into your customer support bot.&lt;/p&gt;

&lt;p&gt;Application-specific evals fill this gap. Instead of testing general capabilities, you test the specific behaviors your application requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the model only use facts from the provided knowledge base?
&lt;/li&gt;
&lt;li&gt;Does it refuse requests that fall outside its intended scope?
&lt;/li&gt;
&lt;li&gt;Does it follow the output format your application expects?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions can only be answered by tests you write yourself, tailored to your application's constraints and requirements.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;When to Use Automated Evals&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Once you understand what evals are, the next question becomes when and how deeply to apply them. Automated evaluations evolve alongside your application, shifting from rapid feedback loops to deep quality assessments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development:&lt;/strong&gt; Rules-based evals provide near-instant feedback on every commit, catching obvious breaks and formatting issues for pennies per run.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging:&lt;/strong&gt; Model-graded evals use "Judge LLMs" to assess nuance and helpfulness on release branches, trading higher costs for deeper insight.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production:&lt;/strong&gt; Continuous evals serve as regression detectors, ensuring that prompt tweaks or model updates don't silently degrade performance over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Core Evaluation Dimensions for LLM Apps&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Regardless of your specific application, most LLM evaluations focus on four dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context adherence:&lt;/strong&gt; Does the output align with the provided context?
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context relevance&lt;/strong&gt;: Is the retrieved context relevant to the user's query?
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correctness&lt;/strong&gt;: Does the output align with ground truth or expected behavior?
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias and toxicity:&lt;/strong&gt; Does the output contain harmful or offensive content?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each dimension requires different evaluation techniques. Context adherence might be tested with keyword matching. Bias detection might require a specialized classifier. The implementation depends on your specific constraints.&lt;/p&gt;
&lt;h2&gt;
  
  
  Case Study: Building a Quiz Generator
&lt;/h2&gt;

&lt;p&gt;To make these concepts concrete, consider an AI-powered quiz generator. The application accepts category requests which include science, geography, or art and returns three quiz questions derived strictly from a predefined dataset. &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The critical constraint&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The model must never generate questions about subjects outside this dataset. If a user requests a quiz about Rome and the dataset contains no Roman history, the correct behavior is refusal not hallucinated content pulled from pre-training. This constraint makes the application testable. We know exactly what inputs should succeed and which should fail. We can write assertions that verify the model respects these boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation: Prompt Design&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Most LLM behavior is controlled by the prompt. A well-designed prompt establishes rules, provides context, and defines failure modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;system_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Follow these steps to generate a customized quiz for the user.

Step 1: Identify the category from: Geography, Science, or Art

Step 2: Select up to two subjects from the quiz bank that match the category

Step 3: Generate 3 questions using only the facts provided

Additional rules:
- Only use explicit matches for the category
- If no information is available, respond: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m sorry I do not have information about that&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure breaks generation into discrete steps, lists valid categories explicitly, and defines refusal behavior preventing hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The LLM Pipeline&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
LangChain provides composable primitives for building the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assistant_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ChatOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;output_parser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;StrOutputParser&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;

    &lt;span class="n"&gt;chat_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChatPromptTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_messages&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;human&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{question}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;chat_prompt&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;output_parser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pipeline serves double duty: it powers the production application &lt;strong&gt;and&lt;/strong&gt; provides a stable interface for automated evaluations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Rules-Based Evaluations
&lt;/h3&gt;

&lt;p&gt;Rules-based evals check for specific patterns in outputs. Here are three essential patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Keyword Matching: Content Validation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Verifies the output contains expected domain terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eval_expected_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_words&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assistant_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;expected_words&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; \
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected words &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_words&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; not found in output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Test case
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_science_quiz&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate a quiz about science.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;expected_subjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;davinci&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telescope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;physics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;eval_expected_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_subjects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assertion is deliberately loose. It passes if *any* expected word appears, accommodating the model's freedom to select different subjects from the valid set. You're not testing for exact phrasing, you're verifying that the model chose appropriate content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Refusal Testing: Hallucination Prevention&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Verifies the model declines out-of-scope requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_refusal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decline_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assistant_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;decline_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; \
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected refusal with &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decline_response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, got: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Test case
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_refusal_rome&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help me create a quiz about Rome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;decline_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m sorry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;evaluate_refusal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decline_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When asked about Rome a topic outside the dataset the model should respond with the specified refusal phrase. If it generates a quiz instead, the test fails, signaling that the prompt's guardrails aren't working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Format Validation: Structural Correctness&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Verifies the output follows expected structure using regex patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eval_quiz_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_questions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify the quiz follows delimiter-based format&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assistant_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;# Pattern to match "Question 1:####", "Question 2:####", etc.
&lt;/span&gt;    &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Question\s+\d+:####&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_questions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; \
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected_questions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; questions in format &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Question N:####&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; \
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Test case
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_science_quiz_format&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate a quiz about science.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;eval_quiz_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_questions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LLMs can "drift" and start returning different formats. If your quiz generator is supposed to use delimiters and suddenly returns markdown or plain text, downstream parsing breaks. &lt;strong&gt;Format enforcement&lt;/strong&gt; prevents the model from returning markdown-wrapped or JSON-formatted output when plain text is expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eval_output_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;should_be_plain_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify output format matches expectations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;  
    &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assistant_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;  

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;should_be_plain_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  
        &lt;span class="c1"&gt;# Should NOT be wrapped in markdown code blocks  
&lt;/span&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\`\`\`&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; \\  
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected plain text, got markdown-wrapped output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  

        &lt;span class="c1"&gt;# Should NOT start with JSON/dict characters  
&lt;/span&gt;        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; \\  
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected plain text, got JSON-like output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Test case  
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_output_is_plain_text&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  
    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate a quiz about art.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  
    &lt;span class="nf"&gt;eval_output_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;should_be_plain_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches a common LLM failure mode: returning content wrapped in markdown code fences (&lt;code&gt;&lt;/code&gt;&lt;code&gt;text ...&lt;/code&gt;&lt;code&gt;&lt;/code&gt;) or unexpectedly switching to JSON format.&lt;br&gt;&lt;br&gt;
Format tests catch issues that keyword tests miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format drift&lt;/strong&gt;: Model updates or prompt changes cause output structure to shift
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration failures&lt;/strong&gt;: If your frontend expects JSON and the LLM returns markdown, your app breaks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster debugging&lt;/strong&gt;: When a test fails, you immediately know whether it's a content issue or a format issue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Integrating Evals into Continuous Integration
&lt;/h3&gt;

&lt;p&gt;Manual testing doesn't scale. As teams grow and development accelerates, the discipline required to run tests before every commit erodes. This is even more critical for LLM applications because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt changes are easy to make but hard to validate without testing
&lt;/li&gt;
&lt;li&gt;Model behavior can shift with version updates
&lt;/li&gt;
&lt;li&gt;Context changes affect outputs in non-obvious ways&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By integrating evals into CI, every change is automatically validated before it reaches production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The GitHub Actions Workflow&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Every push and pull request triggers automated evaluation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Evaluations&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run-evals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run evaluation tests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pytest test.py -v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OpenAI API key is stored as a repository secret, keeping credentials secure while allowing tests to make real API calls. When you push code, GitHub Actions automatically runs your evaluation suite: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ujzpic7ns6y7d9nx2ps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ujzpic7ns6y7d9nx2ps.png" alt=" " width="800" height="458"&gt;&lt;/a&gt; &lt;br&gt;
The workflow shows all tests passing. Any test failure blocks the merge, preventing broken behavior from reaching production.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Limitations of Rules-Based Evaluations&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Rules-based evaluations have clear boundaries. They can verify patterns and keywords but cannot assess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Factual accuracy beyond keyword presence: The test verifies "telescope" appears but not whether the telescope facts are correct
&lt;/li&gt;
&lt;li&gt;Output quality: A response might contain correct keywords but be poorly phrased or confusing
&lt;/li&gt;
&lt;li&gt;Subtle hallucinations: The model might include expected keywords while inserting fabricated details between them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Path Forward&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For production systems, extend this foundation with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model-graded evaluations&lt;/strong&gt;: Use a second LLM to assess subjective qualities (helpfulness, clarity, tone)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias and toxicity detection&lt;/strong&gt;: Integrate specialized classifiers
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt;: Compare prompt variations with data-driven metrics
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production observability&lt;/strong&gt;: Monitor outputs continuously; some failure modes only emerge at scale&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The strategy: Run rules-based evals on every commit, model-graded evals on release branches, and continuous monitoring in production.&lt;/p&gt;

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

&lt;p&gt;LLM applications don't have to be opaque or untestable. By combining explicit constraints, semantic evaluations, and automated execution, you transform probabilistic models into reliable system components. The workflow is straightforward and repeatable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define constraints in prompts
&lt;/li&gt;
&lt;li&gt;Write tests that verify those constraints (keyword, format, refusal)
&lt;/li&gt;
&lt;li&gt;Run evaluations automatically on every change
&lt;/li&gt;
&lt;li&gt;Use rules-based evals for speed, model-graded evals for depth&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result becomes a software you can iterate on quickly, deploy confidently, and maintain sustainably even when it's powered by probabilistic models. See full implementation here (&lt;a href="https://github.com/iamkalio/llmops-eval-ci" rel="noopener noreferrer"&gt;https://github.com/iamkalio/llmops-eval-ci&lt;/a&gt;)  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>Understanding Vectors and Vector Search: How Vector Search Understands What You Really Mean</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Mon, 20 Oct 2025 09:42:33 +0000</pubDate>
      <link>https://dev.to/kalio/understanding-vectors-and-vector-search-how-vector-search-understands-what-you-really-mean-19dk</link>
      <guid>https://dev.to/kalio/understanding-vectors-and-vector-search-how-vector-search-understands-what-you-really-mean-19dk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Imagine you're an engineer looking to optimise your project's database. You might start by searching for "B-Tree indexing" or "database query optimisation." A traditional search engine, operating on keyword-matching principles, would scan its index for documents containing those exact phrases.&lt;/p&gt;

&lt;p&gt;But what if the most groundbreaking article on the topic never uses those words, instead referring to the concept as "SQL tuning strategies" or "efficient data retrieval patterns"? With a simple keyword search, that invaluable resource would remain invisible. This highlights the fundamental limitation of traditional search: it matches words, not ideas. It fails to understand context, intent, or the subtle semantic relationships that connect concepts.&lt;/p&gt;

&lt;p&gt;This conflict between human intent and machine literalism creates a clear need for a more intelligent, intuitive way to discover knowledge.&lt;/p&gt;

&lt;p&gt;In this blog, we'll discuss everything related to vector search, starting with the basic concepts and then moving on to more advanced techniques. Let's begin by overviewing vectors and embeddings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vectors and Embedding 
&lt;/h2&gt;

&lt;p&gt;To understand how a machine can grasp the concept of "similarity," we first need to understand how it represents information. Humans use words and images, but computers speak in numbers. Modern AI lies in its ability to translate our rich, unstructured world into a mathematical format it can process. This translation is achieved through a concept known as vector embeddings.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Vector Embedding?
&lt;/h3&gt;

&lt;p&gt;Vector embedding is a numerical representation of an object: be it a word, a sentence, a complete document, an image, or a piece of audio. These numbers aren't random; they are generated by a machine learning model trained to capture the object's true semantic meaning and context. This process effectively maps data into a high-dimensional space where related items are positioned closer together.&lt;/p&gt;

&lt;p&gt;The relationships captured within this high-dimensional space are so precise that they even support arithmetic operations. A classic demonstration of this is the equation: &lt;code&gt;vector('king') - vector('man') + vector('woman') ≈ vector('queen')&lt;/code&gt;.&lt;br&gt;
This reveals how the model has learned to associate abstract concepts like gender and royalty in a calculable way.&lt;/p&gt;

&lt;p&gt;These numerical representations are technically known as dense vectors, arrays in which most values are non-zero. It's this dense structure that allows them to encode such a rich amount of semantic information, making them the foundational element for any vector search system.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vectorisation Process
&lt;/h3&gt;

&lt;p&gt;Creating these embeddings is a structured process known as vectorisation, which turns raw data into meaningful numerical representations. It follows a clear and methodical path outlined in the section.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Preparation:&lt;/strong&gt; Raw data is messy. The process begins by cleaning and standardising the raw data. This step ensures the model receives high-quality input, whether that means removing irrelevant characters from text or resizing an image to a uniform dimension.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Selection:&lt;/strong&gt; The heart of the vectorisation process is the embedding model. An embedding model, pre-trained on vast datasets, is selected to act as a translator. Popular models include &lt;a href="https://huggingface.co/google-bert/bert-base-uncased" rel="noopener noreferrer"&gt;BERT (Bidirectional Encoder Representations from Transformers)&lt;/a&gt; or &lt;a href="https://www.sbert.net/" rel="noopener noreferrer"&gt;Sentence-BERT&lt;/a&gt;, which are designed to understand the nuances of text. Similarly, &lt;a href="https://huggingface.co/openai/clip-vit-large-patch14" rel="noopener noreferrer"&gt;CLIP (Contrastive Language--Image Pre-training)&lt;/a&gt;,  trained on vast pairings of images and their textual descriptions, is used for interpreting the content of images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generation:&lt;/strong&gt; The prepared data is fed into the selected model. After processing the input through its complex layers, the model outputs the final vector embedding, the data's unique mathematical fingerprint that captures its semantic meaning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Storage &amp;amp; Indexing:&lt;/strong&gt; Finally, the newly created vectors are loaded into a specialised vector database, such as Pinecone, Chroma, etc. This system is purpose-built to store, manage, and index millions of these high-dimensional vectors for fast search and retrieval.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Engine: How Vector Search Works
&lt;/h3&gt;

&lt;p&gt;With our data translated into vectors and stored, the search process can begin. The core principle is quite intuitive: it's all about finding the closest matching vector in a high-dimensional space(a mathematical space defined by many features, like hundreds or thousands of axes, where similar items (vectors) are placed close together). This section breaks down how the engine finds the most relevant results with remarkable speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a. The Fundamental Principle: Finding Similarity&lt;/strong&gt;&lt;br&gt;
When you make a query, whether it's text or an image, it's also converted into a vector using the very same model. The system's job is then to find the vectors in the database that are the closest "neighbors" to your query vector. This process of finding the most similar items based on proximity is known as a &lt;a href="https://en.wikipedia.org/wiki/Nearest_neighbor_search" rel="noopener noreferrer"&gt;Nearest Neighbor (NN)&lt;/a&gt; search. The original items corresponding to these neighboring vectors are the results you see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;b. Measuring "Closeness": Distance Metrics&lt;/strong&gt;&lt;br&gt;
To find the "closest" neighbors, the system needs a way to measure the distance or similarity between two vectors. While there are many methods, two are predominantly used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Euclidean Distance: This is the most straightforward approach. It measures the straight-line distance between two vector points in the high-dimensional space, much like finding the distance between two cities on a map. A smaller distance means the items are more similar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cosine Similarity: Instead of distance, this metric measures the angle between two vectors. It focuses on their orientation, not their magnitude. A smaller angle results in a similarity score closer to 1, indicating a strong match. This is especially useful for text, as it can recognise that a short headline and a long article are about the same topic if they point in the same conceptual direction.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;c. The Challenge of Scale: Why Brute-Force Isn't Enough&lt;/strong&gt;&lt;br&gt;
Calculating the distance between a few vectors is easy. But what about a database with millions or even billions of them? Comparing your query vector to every single one is known as a brute-force search (native search). While this method is perfectly accurate, it becomes incredibly slow and computationally expensive at scale, making it impractical for the real-time results we expect from modern applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;d. The Solution for Speed: Approximate Nearest Neighbor (ANN)&lt;/strong&gt;&lt;br&gt;
To overcome this performance bottleneck, vector search employs a clever trade-off: &lt;a href="https://www.elastic.co/blog/understanding-ann" rel="noopener noreferrer"&gt;Approximate Nearest Neighbor (ANN).&lt;/a&gt; Instead of finding the exact nearest neighbors, ANN algorithms use efficient indexing techniques to find vectors that are almost the nearest, and they do it exponentially faster.&lt;/p&gt;

&lt;p&gt;For most applications, this compromise is ideal. It delivers highly relevant, near-perfect results in milliseconds, avoiding the impossible computational cost of a brute-force search and making vector search a practical, powerful tool. &lt;/p&gt;

&lt;h3&gt;
  
  
  Vector Search in Action: Real-World Applications
&lt;/h3&gt;

&lt;p&gt;The true impact of this engine is seen in the growing number of applications it powers. By understanding meaning, vector search makes our digital experiences more intuitive, relevant, and intelligent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Semantic Search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the most direct application. Instead of being limited by keywords, you can find what you mean, not just what you type. For example, an e-commerce site can interpret a search for "something warm to wear on a cold hike" and return results for fleece jackets, thermal layers, and wool socks, even if those exact words aren't in the product descriptions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Recommendation Systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ever wonder how Spotify discovers your next favourite artist or Netflix suggests the perfect movie? These platforms often use vector search. They create a vector representing your unique taste based on your activity. The recommendation engine then searches for items which include songs, movies, or products with vectors that are closest to your taste profile, delivering highly personalised suggestions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Image &amp;amp; Visual Search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vector search allows for content-based retrieval. Instead of trying to describe a pattern or style with words, you can use an image as your query. A user could upload a photo of a chair they like and instantly find visually similar chairs from an online furniture catalog, a task that would be nearly impossible with keywords alone.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Retrieval-Augmented Generation (RAG) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a critical application for improving Large Language Models (LLMs) like ChatGPT. LLMs are powerful but can be out-of-date or invent incorrect facts ("hallucinate"). RAG uses vector search as a fact-checker. When you ask a question, the RAG system first uses vector search to find the most relevant, factual documents from a private or updated knowledge base. It then gives that information to the LLM along with your question, instructing it to generate an answer based only on the provided facts. This makes the LLM's responses dramatically more accurate and trustworthy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Hybrid Search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hybrid search offers the best of both worlds. It combines the contextual understanding of vector search with the precision of traditional keyword search. For example, when searching for a specific product like an "iPhone 15 Pro case," the keyword "iPhone 15 Pro" is essential. Hybrid search ensures exact matches are prioritised while also using vector similarity to find the most relevant styles and features, delivering the most accurate results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Ultimately, vector search represents a fundamental shift in how we interact with information. It moves us beyond literal keyword matching into a more fluid, human-like understanding of context and meaning. Powered by vector embeddings and the speed of ANN algorithms, this technology is the engine behind a new generation of smarter applications. It's how search tools finally learn to understand intent, how recommendation engines predict our needs, and how AI can be grounded in fact. It's the technology that allows applications to stop just matching words and start understanding our world.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.elastic.co/what-is/vector-search" rel="noopener noreferrer"&gt;https://www.elastic.co/what-is/vector-search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.coveo.com/blog/what-is-vector-search/" rel="noopener noreferrer"&gt;https://www.coveo.com/blog/what-is-vector-search/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.oracle.com/africa/database/vector-search/" rel="noopener noreferrer"&gt;https://www.oracle.com/africa/database/vector-search/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ibm.com/think/topics/vector-search" rel="noopener noreferrer"&gt;https://www.ibm.com/think/topics/vector-search&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@myscale/what-is-vector-search-7d39b7e4d77a" rel="noopener noreferrer"&gt;https://medium.com/@myscale/what-is-vector-search-7d39b7e4d77a&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://weaviate.io/blog/vector-search-explained" rel="noopener noreferrer"&gt;https://weaviate.io/blog/vector-search-explained&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
    </item>
    <item>
      <title>Load Testing a Scalable AWS Application Using Grafana k6</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Wed, 28 May 2025 09:06:00 +0000</pubDate>
      <link>https://dev.to/kalio/load-testing-a-scalable-aws-application-using-grafana-k6-5238</link>
      <guid>https://dev.to/kalio/load-testing-a-scalable-aws-application-using-grafana-k6-5238</guid>
      <description>&lt;p&gt;In cloud-native applications, performance under load is a critical consideration. Ensuring applications can handle expected traffic and unexpected surges is key to maintaining a positive user experience and system stability. Load testing is essential in validating the resilience and scalability of modern applications.&lt;/p&gt;

&lt;p&gt;This tutorial will demonstrate how to use Terraform to set up a scalable application infrastructure on Amazon Web Services (AWS). After the infrastructure has been provisioned, we will use Grafana K6 to conduct a variety of load tests, and we'll execute smoke, average, and spike load tests on the application. We will analyse the performance metrics captured during these tests, such as latency, throughput, and success rates, to understand how the architecture handles different traffic patterns and how its components, like the Auto Scaling Group and Application Load Balancer, contribute to its ability to scale and maintain reliability under stress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequitises 
&lt;/h2&gt;

&lt;p&gt;To effectively follow this guide, ensure you have the following prepared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AWS Account. You'll need an active account, which you can sign up for on the&lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;  AWS website&lt;/a&gt; if you don't have one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configured AWS CLI. Install the AWS Command Line Interface from the official&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;  AWS CLI installation guide&lt;/a&gt; and configure it with your credentials by following the&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html" rel="noopener noreferrer"&gt;  AWS CLI configuration guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terraform CLI Installed: Download and install the Terraform CLI from the official HashiCorp&lt;a href="https://developer.hashicorp.com/terraform/downloads" rel="noopener noreferrer"&gt;  Terraform installation page&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Grafana k6 Installed: Install the k6 load testing tool by downloading it from the&lt;a href="https://www.google.com/search?q=https://k6.io/docs/getting-started/installation/" rel="noopener noreferrer"&gt;  &lt;/a&gt;&lt;a href="https://grafana.com/docs/k6/latest/set-up/install-k6/" rel="noopener noreferrer"&gt;k6.io website.&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS SSH Key Pair: Create an SSH key pair in your AWS region for EC2 access via the&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/create-key-pairs.html" rel="noopener noreferrer"&gt;  EC2 console&lt;/a&gt;, and note its name for your Terraform setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Basic Terraform Understanding: Familiarise yourself with Terraform's core concepts through the&lt;a href="https://developer.hashicorp.com/terraform/intro" rel="noopener noreferrer"&gt;  Terraform documentation&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sufficient IAM Permissions: The AWS identity (&lt;code&gt;user&lt;/code&gt; or &lt;code&gt;role&lt;/code&gt;) that Terraform uses must have permissions to create and manage resources for this project, primarily involving services like VPCs, EC2 instances, ALBs, Auto Scaling Groups, Security Groups, and Subnets. While a broad policy like &lt;code&gt;AdministratorAccess&lt;/code&gt; can be used for initial learning in a personal account (use with caution), the best practice for any environment is to apply the principle of least privilege. This involves creating a custom IAM policy with only the specific permissions needed for Terraform to manage these resources. You can learn how to do this by reviewing the AWS guide on&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html" rel="noopener noreferrer"&gt;  creating IAM policies&lt;/a&gt;. If Terraform encounters permission errors during execution, the error messages will typically guide you on what specific permissions are missing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview of the Scalable Infrastructure
&lt;/h2&gt;

&lt;p&gt;Before we dive into provisioning, let's understand the architecture we'll be building on AWS. This setup is designed for scalability and high availability, ensuring our application can handle varying loads effectively. The key components, as illustrated in the diagram below, include:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf0yLrM_q3gFkK4ltnbswL-9KFs9HCPInq1TOW0nnlkMSzBC22CVC39j-FzcnDi65lN7dkAh_RO6nfE8nCLxdXuZQKiu6_Vq_dcGLgTCwAfnZqpMWUYlA_gfv-RTaqOQQRvGlM1QQ%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf0yLrM_q3gFkK4ltnbswL-9KFs9HCPInq1TOW0nnlkMSzBC22CVC39j-FzcnDi65lN7dkAh_RO6nfE8nCLxdXuZQKiu6_Vq_dcGLgTCwAfnZqpMWUYlA_gfv-RTaqOQQRvGlM1QQ%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="861" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon Web Services (AWS) Cloud:&lt;/strong&gt; Our infrastructure resides entirely within the AWS cloud, leveraging its robust and scalable services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Virtual Private Cloud (VPC):&lt;/strong&gt; We'll establish a custom VPC, which acts as a private, isolated section of the AWS cloud. This provides a secure network environment for our resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internet Gateway (IGW):&lt;/strong&gt; An IGW is attached to our VPC to allow communication between resources in our VPC and the internet. This is crucial for users to access our application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Subnets across Multiple Availability Zones (AZs):&lt;/strong&gt; Within our VPC, we will configure public subnets. To ensure high availability and fault tolerance, these subnets will be distributed across multiple Availability Zones (e.g., AZ a, AZ b, AZ c). If one AZ experiences an issue, our application can continue running in other AZs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application Load Balancer (ALB):&lt;/strong&gt; The ALB serves as the single point of contact for clients and automatically distributes incoming application traffic across multiple targets, such as EC2 instances, in different Availability Zones. This enhances application availability and fault tolerance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EC2 Instances:&lt;/strong&gt; These are virtual servers in the AWS cloud where our simple web application will run. The application itself will be deployed using EC2 user data scripts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto Scaling Group (ASG):&lt;/strong&gt; The ASG is the core of our scalability. It automatically adjusts the number of EC2 instances running our application based on predefined conditions (like CPU utilisation or network traffic). If the load increases, the ASG launches more instances; if the load decreases, it terminates instances to save costs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Target Group:&lt;/strong&gt; The Application Load Balancer uses this group to route requests to one or more registered targets, which in our case are the EC2 instances managed by the Auto Scaling Group. The ALB checks the health of instances in the target group and only sends traffic to healthy instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EC2 Security Group:&lt;/strong&gt; This acts as a virtual firewall for our EC2 instances, controlling inbound and outbound traffic. We'll configure it to allow traffic from the Application Load Balancer and necessary administrative access (e.g., SSH, if needed for debugging, though not directly used by the load test traffic itself).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture ensures that incoming internet traffic passes through the IGW to the ALB. The ALB then distributes this traffic across healthy EC2 instances running in public subnets across different AZs. The Auto Scaling Group monitors these instances and scales the fleet in or out based on demand, providing both resilience and cost-effectiveness. Our Terraform scripts will define and provision all these components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provisioning the infrastructure with Terraform
&lt;/h2&gt;

&lt;p&gt;This section outlines the steps in provisioning your application's infrastructure on AWS using Terraform. Terraform allows you to define your infrastructure as code, making the provisioning process repeatable, predictable, and versionable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloning the infrastructure repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first step is to obtain the Terraform code that defines your AWS infrastructure.&lt;/p&gt;

&lt;p&gt;1.Open your terminal.&lt;/p&gt;

&lt;p&gt;2.Navigate to the directory where you want to clone the repository.&lt;/p&gt;

&lt;p&gt;3.Clone the repository containing the Terraform configuration. Run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/iamkalio/infra-load-testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.Change into the cloned repository directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cd &lt;/span&gt;infra-load-testing/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This repository contains the Terraform configuration files (&lt;code&gt;.tf&lt;/code&gt; files) that describe the desired state of your AWS resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provisioning your infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you have the infrastructure code locally, you can use Terraform to provision the resources on AWS. Ensure you have the AWS CLI configured with appropriate credentials and permissions to create resources in your desired region.&lt;/p&gt;

&lt;p&gt;1.&lt;strong&gt;Initialise your working directory:&lt;/strong&gt; To initialise the directory and download the necessary provider plugins, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On successful initialisation, the &lt;code&gt;.terraform&lt;/code&gt; and &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; files would be added to your directory. &lt;/p&gt;

&lt;p&gt;2.&lt;strong&gt;Review the execution plan:&lt;/strong&gt; Before making any changes to your infrastructure, review the plan that Terraform will execute. To see which resources will be created, modified, or destroyed, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf9Fs5FSxRWZqZDoCzJklSdnJLmKmlj5RAqEiyzRAWX6TTbZYLFwSOuvn-jhygU2Tq6InQ2_8V0RohIHbxJ1WdX8aYt0F-2gr4BWUwbBHO2Pi60ODsMI2HMHjoqgV-YzCiTY_D9ww%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf9Fs5FSxRWZqZDoCzJklSdnJLmKmlj5RAqEiyzRAWX6TTbZYLFwSOuvn-jhygU2Tq6InQ2_8V0RohIHbxJ1WdX8aYt0F-2gr4BWUwbBHO2Pi60ODsMI2HMHjoqgV-YzCiTY_D9ww%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="985"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Carefully examine the output to ensure that Terraform intends to make the changes you expect.&lt;/p&gt;

&lt;p&gt;3.&lt;strong&gt;Apply the configuration:&lt;/strong&gt; Apply the configuration to provision the resources on AWS. Run this command to begin the process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will prompt you to confirm the action. Type &lt;code&gt;yes&lt;/code&gt; and press Enter to proceed. Terraform will then create the defined infrastructure resources in your AWS account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdIUNDWV483-4bQq51EsggSF7scBQmzlqWWQ-pX6UuZDNqZdyYnCswLAjDUDz8Nrt4YqyXoj_IDqoaRbFlmC_FzRzowI6xEscbmcRMPPo0PNEBV6rk8iWeM--XejmS-2FfNTRGSXg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdIUNDWV483-4bQq51EsggSF7scBQmzlqWWQ-pX6UuZDNqZdyYnCswLAjDUDz8Nrt4YqyXoj_IDqoaRbFlmC_FzRzowI6xEscbmcRMPPo0PNEBV6rk8iWeM--XejmS-2FfNTRGSXg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="931"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pasting the ALB DNS name into your browser lets you view your active application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTt69ucXYzVW8EVloGfZ3Bcu5H68X7MvoQda_MOUphmG9tEeavCMN8aX3ugTjUWJVZi0_cdtMF7aN1VE8Vhce524nhNsomuzdV1cb0gsfOj4zuCiBm8lFI5-VBWVv8xobtBRyqgA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTt69ucXYzVW8EVloGfZ3Bcu5H68X7MvoQda_MOUphmG9tEeavCMN8aX3ugTjUWJVZi0_cdtMF7aN1VE8Vhce524nhNsomuzdV1cb0gsfOj4zuCiBm8lFI5-VBWVv8xobtBRyqgA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note the ALB DNS from the output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After a successful &lt;code&gt;terraform apply&lt;/code&gt;, Terraform will output the values of any defined outputs in your configuration. It is common practice to output the DNS name of the Application Load Balancer (ALB), as this is the entry point for traffic directed towards your application running on the provisioned infrastructure.&lt;/p&gt;

&lt;p&gt;Look for the outputs section in the console output after &lt;code&gt;terraform apply&lt;/code&gt; completes. Identify and note down the DNS name listed for your ALB ( &lt;code&gt;alb_dns_name&lt;/code&gt;). This is the URL you will use to access your deployed application once it's running on the provisioned infrastructure.&lt;/p&gt;

&lt;p&gt;To retrieve this output value at any time after provisioning, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  terraform output alb_dns_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdi7t5YgwMVtcthU6qJNfhPJ-jvSAklx64W2IqzdqgPVcAuCjNPO1WI6GEzDdMXXMDufhArmifImwo93fdDjTw1Qej7LvZqTD3oLMXnJ3AesU7IH5W9Pttcm9--smvDumQt36Nr3g%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdi7t5YgwMVtcthU6qJNfhPJ-jvSAklx64W2IqzdqgPVcAuCjNPO1WI6GEzDdMXXMDufhArmifImwo93fdDjTw1Qej7LvZqTD3oLMXnJ3AesU7IH5W9Pttcm9--smvDumQt36Nr3g%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1252" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Showcasing the infrastructure that is created on AWS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After successfully running &lt;code&gt;terraform apply&lt;/code&gt;, the infrastructure defined in your code will be active in your AWS account. You can verify and explore the created resources using the AWS Management Console or the AWS CLI.&lt;/p&gt;

&lt;p&gt;The infrastructure provisioned includes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcjkfMLZyIkGrzMziqe9F_9iQ81antORsFZoSDaa86mYe0gBKo3X59MqQBWKYwF0a11u6gx11LX7p5F1AZaqojFv7F-7C0SVw13YeUt-W-BohgVkytSf4eaCPuWfg0noLXC_dgK%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcjkfMLZyIkGrzMziqe9F_9iQ81antORsFZoSDaa86mYe0gBKo3X59MqQBWKYwF0a11u6gx11LX7p5F1AZaqojFv7F-7C0SVw13YeUt-W-BohgVkytSf4eaCPuWfg0noLXC_dgK%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="871"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Virtual Private Cloud (VPC):&lt;/strong&gt; A logically isolated network for your resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdi5KMwAqP5da_kDcxZayT3AcyS_W7SVyKhw7IGk2zafHeBTxU2GfXURcNavTWCb9FAiR9Eiiwoy4cyrWwwBkNnG3Ku3_i-14periMrpNiWjpcXiK1vuBXnRif6rweCf0xRdRjPSg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdi5KMwAqP5da_kDcxZayT3AcyS_W7SVyKhw7IGk2zafHeBTxU2GfXURcNavTWCb9FAiR9Eiiwoy4cyrWwwBkNnG3Ku3_i-14periMrpNiWjpcXiK1vuBXnRif6rweCf0xRdRjPSg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Subnets:&lt;/strong&gt; Divisions within your VPC (e.g., public subnets for load balancers and private subnets for application instances and databases). In this case, it is a public subnet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXd8SzMe9PuDzgsUYMOqwxJKJf7SKXX0qktWiE5I6LrKcK5-618MoqcNsCQ4el5jNp8pCKsFEAOK08AwXJWgOR_bgAvqjYzfHi025cemMxxHl17Bx1n2OFyMYl1zy7VYM1nEiiHf%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXd8SzMe9PuDzgsUYMOqwxJKJf7SKXX0qktWiE5I6LrKcK5-618MoqcNsCQ4el5jNp8pCKsFEAOK08AwXJWgOR_bgAvqjYzfHi025cemMxxHl17Bx1n2OFyMYl1zy7VYM1nEiiHf%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="862"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Internet Gateway:&lt;/strong&gt; Allows communication between your VPC and the internet (usually associated with public subnets).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeDvl6EKVO7H6nQGREVQDZ4yiMofYRZc9AusqbdO75l082xnB9ojN4hDWHYwXeyRvDC6VZdaRbsHXOU7uSlBhZcDfz5ObBir0b1QiuDWrvuaFcmzs_ZsqyONZ0h-jWGOP1Geas-ew%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeDvl6EKVO7H6nQGREVQDZ4yiMofYRZc9AusqbdO75l082xnB9ojN4hDWHYwXeyRvDC6VZdaRbsHXOU7uSlBhZcDfz5ObBir0b1QiuDWrvuaFcmzs_ZsqyONZ0h-jWGOP1Geas-ew%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="917"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Route tables:&lt;/strong&gt; Control the routing of network traffic within your VPC and to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdlquKtDnGpWufwuqivFi9KxHvDd9sK0Z6x2XhiddPp5hntKMkFqtDQa8R33_lqJ61rOwSvs0sWm5SuIUC-9vKX6LoB3YRuvIlPFrtim9Tx9_9VHj0xnTOCyb4-zXAMfDUBhG6cmw%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdlquKtDnGpWufwuqivFi9KxHvDd9sK0Z6x2XhiddPp5hntKMkFqtDQa8R33_lqJ61rOwSvs0sWm5SuIUC-9vKX6LoB3YRuvIlPFrtim9Tx9_9VHj0xnTOCyb4-zXAMfDUBhG6cmw%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="811"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Security groups:&lt;/strong&gt; Act as virtual firewalls, controlling inbound and outbound traffic for your instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdWRR7AeJG5QAZ3HBVeIDccg3CZrFk-TZa4VGT2FTZdqKFM9ruh1eF-PRCkC5dvYZr4NMpLY9YDHCTra4PWgzLlZix2-Zsuf8vHnn2GupeiXgBBq6EpPpidQFw30thzD1tZU0Nq4w%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdWRR7AeJG5QAZ3HBVeIDccg3CZrFk-TZa4VGT2FTZdqKFM9ruh1eF-PRCkC5dvYZr4NMpLY9YDHCTra4PWgzLlZix2-Zsuf8vHnn2GupeiXgBBq6EpPpidQFw30thzD1tZU0Nq4w%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Application Load Balancer (ALB):&lt;/strong&gt; Distributes incoming application traffic across multiple targets, such as EC2 instances or containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfTcQw_Fu400dsSZf_nlR6eTjw9cc9ax6SeP8r028hdTk3Va9CGDieykQF_FpyOEjfkXVmScAHentGU_dymqERf5zLnSrY6jHcPMlRNJTNZh49A42uKv97hcWhmvyVbAnDuz8VdLw%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfTcQw_Fu400dsSZf_nlR6eTjw9cc9ax6SeP8r028hdTk3Va9CGDieykQF_FpyOEjfkXVmScAHentGU_dymqERf5zLnSrY6jHcPMlRNJTNZh49A42uKv97hcWhmvyVbAnDuz8VdLw%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="898"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ALB target group:&lt;/strong&gt; A logical grouping of targets (e.g., EC2 instances) that the ALB routes traffic to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcCah-vrBzq8ozxyM0PUkCDMVJWwsNIdtK41O3IHEhg-jtFW3t3msj_NDhj1IsRQrjp4aCM2cceXAwwd90Is57BtHrwl8_3gvDd-GzK7IIzw5LoWOnG2L37_FMADYhkEXG_3Byf%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcCah-vrBzq8ozxyM0PUkCDMVJWwsNIdtK41O3IHEhg-jtFW3t3msj_NDhj1IsRQrjp4aCM2cceXAwwd90Is57BtHrwl8_3gvDd-GzK7IIzw5LoWOnG2L37_FMADYhkEXG_3Byf%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="821"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Compute resources:&lt;/strong&gt; EC2 instances, ECS tasks, or EKS pods where your application will run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXezVywfVKNyR3L0dMtNhGFwc5Nk1w1X7GYG8gS4xL3wXQMM16LgjB_2PxHZI7ru4RnZLKIQMgvrut8EdC2Q_wXUkLREwQmMRh4RbY58tdSI6T5evxrGdDvfQ8T_pO3X4CuEIrZuXA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXezVywfVKNyR3L0dMtNhGFwc5Nk1w1X7GYG8gS4xL3wXQMM16LgjB_2PxHZI7ru4RnZLKIQMgvrut8EdC2Q_wXUkLREwQmMRh4RbY58tdSI6T5evxrGdDvfQ8T_pO3X4CuEIrZuXA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="912"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Auto Scaling group:&lt;/strong&gt; Manages the desired number of healthy instances for your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running the load tests
&lt;/h2&gt;

&lt;p&gt;Now that your application infrastructure is provisioned, you can create a load test script using k6 to simulate user traffic. This script will define the behaviour of virtual users accessing your application through the Application Load Balancer (ALB). Our load testing process will include a smoke test, an average load test, and a spike test to evaluate the application's performance across different scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing a smoke test
&lt;/h3&gt;

&lt;p&gt;Let's begin with a quick smoke test. Smoke test is a lightweight load test designed to verify that your application is running, accessible, and responds correctly to basic requests. It's a crucial first step to catch any fundamental issues early on.&lt;/p&gt;

&lt;p&gt;You will need a place to save your k6 test scripts. It's a good practice to keep your load test scripts separate from your infrastructure code.&lt;/p&gt;

&lt;p&gt;1.Navigate to a suitable location outside of your Terraform infrastructure directory.&lt;/p&gt;

&lt;p&gt;2.Create a directory for your load test scripts. You can name it &lt;code&gt;load-tests&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;mkdir &lt;/span&gt;load-tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Change into the &lt;code&gt;load-tests&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cd &lt;/span&gt;load-tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.Create a new file named &lt;code&gt;smoke-test.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;5.Populate this file with the following k6 script. Remember to replace &lt;code&gt;http://YOUR_ECB_ALB_DNS_NAME/&lt;/code&gt; with the actual DNS name of your ALB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Key for Smoke test. Keep it at 2, 3, max 5 VUs&lt;/span&gt;
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// This can be shorter or just a few iterations&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Replace 'http://YOUR_ECB_ALB_DNS_NAME/' with your actual public IP and port&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urlRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://YOUR_ECB_ALB_DNS_NAME/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script sets up a basic test scenario. It uses import statements for the necessary k6 modules. The &lt;code&gt;options&lt;/code&gt; object configures the test to use a small number of virtual users (vus: 3) for a short duration: '1m'. The default function defines the actions each virtual user will perform: making an HTTP GET request to your application's URL, using &lt;code&gt;check&lt;/code&gt; to ensure the response status is 200, and pausing with sleep(1).&lt;/p&gt;

&lt;p&gt;To run the script, open your terminal, ensure you are in your &lt;code&gt;load-tests&lt;/code&gt; folder, and execute the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k6 run smoke-test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe86TTc-NffH8LK__DbvypuqOkYB30SOIt9P5YshTMKbJljcVB_embE7pfZSXz1CYjcVxGQOCjy8vIeaievNdDCxqmqvYhS9wxGyMGcfbjmsR1hCMA1Gff6rnVdnsygxwRSLLYA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe86TTc-NffH8LK__DbvypuqOkYB30SOIt9P5YshTMKbJljcVB_embE7pfZSXz1CYjcVxGQOCjy8vIeaievNdDCxqmqvYhS9wxGyMGcfbjmsR1hCMA1Gff6rnVdnsygxwRSLLYA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="912"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the test completes, k6 will output results summarising performance. For this smoke test, with 3 virtual users running for approximately 1 minute, the results confirmed the application's basic functionality and responsiveness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP request duration:&lt;/strong&gt; The average request duration was &lt;strong&gt;238.68ms&lt;/strong&gt;, with 95% of requests completing within &lt;strong&gt;279.67ms&lt;/strong&gt;. The maximum duration was &lt;strong&gt;852.69ms&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checks and failures:&lt;/strong&gt; All &lt;strong&gt;143 checks succeeded&lt;/strong&gt;, resulting in a &lt;strong&gt;100.00% success rate&lt;/strong&gt; and &lt;strong&gt;0.00% HTTP request failures&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Throughput:&lt;/strong&gt; The test generated a total of &lt;strong&gt;143 HTTP requests&lt;/strong&gt; at a rate of approximately &lt;strong&gt;2.36 requests per second&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These results demonstrate a healthy and responsive application under light load. Concurrently, observing your AWS CloudWatch metrics during the test will show the request count received by your Application Load Balancer (ALB).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcHL9tW5cTSwlPvxNxx3Mb5q_IRVareB4e_N9Om7SOUW9ji5ufc5EAIAIzxq6-WerW0pReFKLrZ130Ka-UraGqeTE6niGyTPSJu-NCqrA01Iv0nriR8Xk2Cyg5Zere4H-Zes_jXqQ%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcHL9tW5cTSwlPvxNxx3Mb5q_IRVareB4e_N9Om7SOUW9ji5ufc5EAIAIzxq6-WerW0pReFKLrZ130Ka-UraGqeTE6niGyTPSJu-NCqrA01Iv0nriR8Xk2Cyg5Zere4H-Zes_jXqQ%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="856"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Performing an average load test
&lt;/h3&gt;

&lt;p&gt;Next, simulate an average level of traffic that your application might typically handle. This test runs over a longer duration and gradually increases the load to sustained levels, providing insights into performance under expected conditions.&lt;/p&gt;

&lt;p&gt;1.Create a new file named &lt;code&gt;average-load-test.js&lt;/code&gt; in your &lt;code&gt;load-tests&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;2.Add the following k6 script to it, making sure to replace the placeholder URL with your actual target (the ALB DNS name).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="nx"&gt;http&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;options&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// Key configurations for avg load test in this section&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// traffic ramp-up from 1 to 100 users over 5 minutes.&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// stay at 100 users for 30 minutes&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// ramp-down to 0 users&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;urlRes&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://YOUR_ALB_DNS_NAME/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;  &lt;span class="o"&gt;===&lt;/span&gt;  &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What does this script do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This script uses the &lt;code&gt;stages&lt;/code&gt; option to define different phases of the test, simulating a more realistic traffic pattern over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ramp-up (5 minutes): The number of virtual users linearly increases from the default (usually 1) up to &lt;code&gt;target: 100&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sustained load (30 minutes): The test maintains a constant load of &lt;code&gt;target: 100&lt;/code&gt; virtual users. This is the core of the average load test, measuring performance under steady, typical traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ramp-down (5 minutes): The number of virtual users decreases from 100 down to &lt;code&gt;target: 0&lt;/code&gt;, gracefully ending the test.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default function remains the same as the smoke test, performing the basic request and status check for each user.&lt;/p&gt;

&lt;p&gt;3.Execute the average load test script from your &lt;code&gt;load-tests&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  k6 run average-load-test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdz1cin48ABjrU8Ew57ILPYphtcVZRLQQ0cG_VQZFN_-j51vEH7otY8jP92pEulnprt-XTLAHS6KwAYStkuFjRtpG_4wFYNLSTsSTcCPC9jh4gmS0mix7FHSXbN52lkK4ctqHqC%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdz1cin48ABjrU8Ew57ILPYphtcVZRLQQ0cG_VQZFN_-j51vEH7otY8jP92pEulnprt-XTLAHS6KwAYStkuFjRtpG_4wFYNLSTsSTcCPC9jh4gmS0mix7FHSXbN52lkK4ctqHqC%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="995"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Average load test results:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This test ran for approximately &lt;strong&gt;40 minutes&lt;/strong&gt; and aimed to simulate &lt;strong&gt;100 virtual users&lt;/strong&gt;. The results show &lt;strong&gt;168,556 total HTTP requests&lt;/strong&gt; were made, with &lt;strong&gt;99.99%&lt;/strong&gt; of checks succeeding. The average HTTP request duration was &lt;strong&gt;245.49ms&lt;/strong&gt;, with &lt;strong&gt;95%&lt;/strong&gt; of requests completing within &lt;strong&gt;301.52ms&lt;/strong&gt;. Notably, the test recorded a very high maximum request duration of 1 minute and 8 seconds, and some &lt;code&gt;Request Failed&lt;/code&gt; warnings indicated timeouts. Despite these outliers, the overall success rate for HTTP requests was &lt;strong&gt;0.00% failed&lt;/strong&gt; (representing 2 failures out of over 168,000 requests), indicating the application largely handled the sustained load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdq9uVothzp4Y09kdlJf6dQKDmUd8mcXr2ame5gYyfaEbGRmcYWH68-lT528FqZ8xyaW62irZkrZ7J2wVUh_MCxpE_4z3BHtP5xzDBlikzcMDfqy8ZRbQ6oE44tlKDZhw00JtF-VA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdq9uVothzp4Y09kdlJf6dQKDmUd8mcXr2ame5gYyfaEbGRmcYWH68-lT528FqZ8xyaW62irZkrZ7J2wVUh_MCxpE_4z3BHtP5xzDBlikzcMDfqy8ZRbQ6oE44tlKDZhw00JtF-VA%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Concurrently, CloudWatch metrics for "Total requests acknowledged" by your ALB showed the traffic pattern of ramp up, sustained load, and ramp down, confirming the load balancer successfully received the requests. The total requests recorded by CloudWatch over this period exceeded &lt;strong&gt;307,000&lt;/strong&gt;, reflecting the cumulative traffic handled by the ALB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Perform a spike load test
&lt;/h3&gt;

&lt;p&gt;Finally, conduct a spike test. This simulates a sudden, dramatic surge in traffic. It's designed to reveal how your application behaves under extreme bursts of load and its ability to recover.&lt;/p&gt;

&lt;p&gt;1.Create a new file named &lt;code&gt;spike-load-test.js&lt;/code&gt; in your &lt;code&gt;load-tests&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;2.Add the following k6 script, replacing the placeholder URL with your target (ALB DNS name).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="nx"&gt;http&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;options&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// Key configurations for spike in this section&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// fast ramp-up to a high point&lt;/span&gt;

&lt;span class="err"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;// No plateau&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// quick ramp-down to 0 users&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="c1"&gt;// Replace 'http://YOUR_ALB_DNS_NAME/' with your actual ALB DNS name&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;  &lt;span class="nx"&gt;urlRes&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://YOUR_ALB_DNS_NAME/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urlRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;  &lt;span class="o"&gt;===&lt;/span&gt;  &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spike load script uses &lt;code&gt;stages&lt;/code&gt; to create a sudden, sharp increase and decrease in virtual users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Rapid ramp-up (2 minutes): The number of virtual users quickly increases from the default up to a very high target: 2000. This simulates a sudden traffic event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rapid ramp-down (1 minute): The number of users drops just as quickly back to target: 0. There is no sustained load phase in a typical spike test.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default function remains the same, performing the request and status check.&lt;/p&gt;

&lt;p&gt;3.Execute the spike test script from your &lt;code&gt;load-tests&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  k6 run spike-load-test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcHtaORbAVJPFsIwg_h2k8ULx-wr298NRPj-t6NqZhTU4Z6bbzJCBMr0D8Xccknj2_aPUaYS09eVki8pKempTNGDM-vLZEOnw-5Ed91O0DGjkosM71320jwGDvOB80ZsUxnuGI46A%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcHtaORbAVJPFsIwg_h2k8ULx-wr298NRPj-t6NqZhTU4Z6bbzJCBMr0D8Xccknj2_aPUaYS09eVki8pKempTNGDM-vLZEOnw-5Ed91O0DGjkosM71320jwGDvOB80ZsUxnuGI46A%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="919"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This test completed relatively quickly, running for approximately &lt;strong&gt;3 minutes&lt;/strong&gt;. Under such a rapid and high surge in traffic, the test successfully simulated up to &lt;strong&gt;2000 virtual users&lt;/strong&gt;. The results show &lt;strong&gt;138,618 total HTTP requests&lt;/strong&gt; were made, with a &lt;strong&gt;100.00% success rate&lt;/strong&gt; for all checks and &lt;strong&gt;0.00% HTTP request failures&lt;/strong&gt;. The average HTTP request duration was &lt;strong&gt;383.01ms,&lt;/strong&gt; with 95% of requests completing within 646.53ms. The maximum duration observed was 4.29 seconds, indicating some requests experienced noticeable delays during the peak load. The system sustained a high throughput of approximately &lt;strong&gt;766 requests per second&lt;/strong&gt; during the test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe0YUm-M8roZy5GcYfuuczjTJbWVi4MyvLY9-ekn_f-XSpU7Lkx7CPKqmyJYdmTS1jldZpHRnTCriq2fgRZqiM5eHH8KRAbVtb4lZht4NnIANMwRlnrqCC203sHCmomIEmVSGsxDg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe0YUm-M8roZy5GcYfuuczjTJbWVi4MyvLY9-ekn_f-XSpU7Lkx7CPKqmyJYdmTS1jldZpHRnTCriq2fgRZqiM5eHH8KRAbVtb4lZht4NnIANMwRlnrqCC203sHCmomIEmVSGsxDg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1600" height="916"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CloudWatch metrics for "Total requests acknowledged" by your ALB clearly showed a sharp peak of over &lt;strong&gt;120,000 requests&lt;/strong&gt; during the spike, followed by a rapid drop-off. This pattern mirrored the intense traffic load generated by k6, confirming that the ALB successfully handled the sudden surge in traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analysing Performance
&lt;/h3&gt;

&lt;p&gt;The k6 load tests provide insight into how the provisioned infrastructure performs under varying traffic.&lt;/p&gt;

&lt;p&gt;The architecture works by having the ALB distribute incoming requests across multiple instances. To handle increased traffic, the ASG automatically adjusts the number of instances running your application. Automatic scaling is driven by CloudWatch metrics like requests per target or CPU utilisation. When these metrics cross defined thresholds, CloudWatch alarms trigger scaling policies in the ASG. This tells the ASG to launch new instances. These new instances automatically register with the ALB, and the load is spread further.&lt;/p&gt;

&lt;p&gt;This scaling process directly impacts the performance metrics observed in your k6 tests in several ways. As the ASG adds more instances, the load on each instance decreases. This allows requests to be processed faster, reducing overall latency, especially during traffic spikes. With more capacity available, instances are less likely to become overwhelmed. This reduces errors and dropped connections, leading to a higher percentage of successful requests even under significant load. Increased instances also mean the system can handle a higher total volume of requests per second sustainably, known as throughput. The ALB's health checks also ensure traffic only goes to healthy instances, contributing to overall reliability.&lt;/p&gt;

&lt;p&gt;The k6 test results, particularly from the average and spike tests, demonstrate how effectively the ASG responds to handle the load. By scaling out, the architecture maintains performance and high reliability even during bursts of traffic. This dynamic adjustment of resources based on demand is a key benefit of the scalable infrastructure provisioned by Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices for load testing applications
&lt;/h2&gt;

&lt;p&gt;To ensure effective load testing and derive meaningful insights from your applications, follow these best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always test in a staging or test environment:&lt;/strong&gt; Avoid running load tests directly on production systems unless necessary and with extreme caution. Use an isolated environment that closely mirrors your production setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use realistic traffic patterns:&lt;/strong&gt; Design your k6 scripts to mimic real user behaviour as closely as possible, including request types, request frequency, and user concurrency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start small and gradually increase the load:&lt;/strong&gt; Begin with a low number of virtual users and gradually ramp up the load. This helps you identify bottlenecks incrementally and observe how your system scales.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitor AWS metrics alongside k6 output:&lt;/strong&gt; Combine the client-side metrics from k6 with server-side metrics from AWS CloudWatch. This provides a holistic view, showing how your infrastructure (ALB, EC2/ECS/EKS, ASG, RDS) responds to the load.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider chaos testing for resilience validation:&lt;/strong&gt; Once stability under load is confirmed, introduce controlled failures (e.g., terminating instances) to validate your system's resilience and recovery capabilities.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cleaning up your provisioned resources
&lt;/h2&gt;

&lt;p&gt;After completing your load tests and analysis, it is crucial to tear down the infrastructure you provisioned with Terraform to avoid ongoing AWS charges.&lt;/p&gt;

&lt;p&gt;1.Navigate back to your Terraform infrastructure directory.&lt;/p&gt;

&lt;p&gt;2.Destroy the provisioned resources by running the &lt;code&gt;terraform destroy&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Terraform will display a plan of all resources that will be destroyed. Type &lt;code&gt;yes&lt;/code&gt; and press Enter to confirm the destruction of your AWS infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcaZcSWDlFmiU3QmkrebnEYScXzzNXHPV9zkcwvbhv4P-TZekTLm_a_EXzGnnZMspXVX3oRwrvd5b8OYBp1lw5OQlqig0ws60F73wENAsdUn1hWpMpiQ0tlGfyFpaJaDub96apDyg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcaZcSWDlFmiU3QmkrebnEYScXzzNXHPV9zkcwvbhv4P-TZekTLm_a_EXzGnnZMspXVX3oRwrvd5b8OYBp1lw5OQlqig0ws60F73wENAsdUn1hWpMpiQ0tlGfyFpaJaDub96apDyg%3Fkey%3DBJJChJZ_JrJ3-tvTXFkpgQ" width="1188" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This command will safely de-provision all the AWS resources that Terraform created, ensuring no unnecessary costs are incurred.&lt;/p&gt;

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

&lt;p&gt;In this guide, we demonstrated an approach to validating the performance and scalability of cloud-native applications. We covered the entire process, including setting up a cloud infrastructure on AWS using Terraform, performing various load tests with Grafana k6, from basic smoke tests to more intensive average and spike scenarios, and finally, analyzing the performance metrics to understand how this architecture effectively handles diverse traffic conditions, especially through its auto scaling capabilities driven by CloudWatch.&lt;/p&gt;

&lt;p&gt;The key takeaway is that performance under load is paramount for modern applications. Implementing a scalable infrastructure with components like auto scaling groups is critical for ensuring reliability, consistent latency, and high throughput. Combining regular load testing with robust monitoring is essential to confirm your application can dynamically adapt to demand and provide a seamless user experience.&lt;/p&gt;

</description>
      <category>k6</category>
      <category>terraform</category>
      <category>devops</category>
      <category>loadtesting</category>
    </item>
    <item>
      <title>Provisioning an AWS EC2 Instance with Terraform</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Sat, 24 May 2025 20:27:43 +0000</pubDate>
      <link>https://dev.to/kalio/provisioning-an-ec2-instance-with-terraform-5737</link>
      <guid>https://dev.to/kalio/provisioning-an-ec2-instance-with-terraform-5737</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) is becoming a key part of modern cloud engineering. It lets developers and DevOps engineers provision and manage cloud resources consistently and efficiently. Among the most popular IaC tools, Terraform stands out for its declarative syntax, strong community support, and multi-cloud capabilities.&lt;/p&gt;

&lt;p&gt;This article provides a step-by-step guide to help you provision your first EC2 instance on AWS using Terraform. Whether you're a beginner exploring Terraform for the first time or someone looking to solidify your AWS provisioning workflow, this guide walks you through the entire process, from setting up your AWS environment to writing and applying Terraform configuration files. By the end, you'll understand how to securely configure access, provision an EC2 instance, and clean up resources while adhering to best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite 
&lt;/h2&gt;

&lt;p&gt;To get the most out of this article, you must have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An AWS Account: You need an active AWS account. If you don't have one, sign up on the&lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;  AWS website&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS CLI: Install the AWS Command Line Interface from the&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html" rel="noopener noreferrer"&gt;  official AWS CLI installation guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terraform CLI Installed: Download and install the Terraform CLI from the&lt;a href="https://developer.hashicorp.com/terraform/downloads" rel="noopener noreferrer"&gt;  official HashiCorp Terraform installation page&lt;/a&gt;. Verify your installation by running &lt;code&gt;terraform --version&lt;/code&gt; in your terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terraform extension (optional): Install&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;  VS Code&lt;/a&gt; and the&lt;a href="https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform" rel="noopener noreferrer"&gt;  official Terraform extension&lt;/a&gt; for improved syntax highlighting and features.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Your AWS Environment
&lt;/h2&gt;

&lt;p&gt;Before Terraform can provision resources on AWS, it needs to authenticate with your AWS account. You'll generate an Access Key ID and a Secret Access Key using AWS Identity and Access Management (IAM) for this purpose. Follow these steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an IAM User&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To securely manage access to your AWS resources, begin by creating a dedicated IAM user that Terraform will use for authentication. This will ensure that you avoid using your root account credentials.&lt;/p&gt;

&lt;p&gt;1.Log in to your AWS Management Console. In the search bar, type &lt;strong&gt;IAM&lt;/strong&gt; and select the &lt;strong&gt;IAM service&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;2.On the left sidebar, click on &lt;strong&gt;Users&lt;/strong&gt;, then select &lt;strong&gt;Create User&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;3.Enter a preferred username (e.g.&lt;code&gt;terraform-admin&lt;/code&gt;) and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTNp2sn-ec38wQWvvebmecVe930whcfsqo671nLVexpudfJicqwa3IhGUeowsDk839bu1nexslGOlm-x2a-diy9rhfaCoei548F2JFmkHJeiDiPacgVTUDEkOzPCumRywysuT3oA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTNp2sn-ec38wQWvvebmecVe930whcfsqo671nLVexpudfJicqwa3IhGUeowsDk839bu1nexslGOlm-x2a-diy9rhfaCoei548F2JFmkHJeiDiPacgVTUDEkOzPCumRywysuT3oA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1600" height="953"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.On the &lt;strong&gt;Set Permissions&lt;/strong&gt; page, attach the &lt;strong&gt;AdministratorAccess&lt;/strong&gt; policy directly to the user. For this tutorial, we're using AdministratorAccess for simplicity, but in a production environment, always apply the&lt;a href="https://aws.amazon.com/architecture/security-identity-compliance/" rel="noopener noreferrer"&gt; Principle of Least Privilege&lt;/a&gt; by granting only the minimum necessary permissions. If you already have an IAM group with the required permissions, you can add the new user to that group instead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcuFohNCpllVX27mK1-DutSGukEjLYisv7aNSDO29t-ki1KhcxN4crhVwt2LMD85DR7UY-aOizYEV7Vu_G2rgUigBYcAtWnEA75nRph9dXEJb9AhY59cvqJVOHm5pw-YLSN7vPwKQ%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcuFohNCpllVX27mK1-DutSGukEjLYisv7aNSDO29t-ki1KhcxN4crhVwt2LMD85DR7UY-aOizYEV7Vu_G2rgUigBYcAtWnEA75nRph9dXEJb9AhY59cvqJVOHm5pw-YLSN7vPwKQ%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.Review the permissions, click &lt;strong&gt;Next&lt;/strong&gt;, and then &lt;strong&gt;Create User&lt;/strong&gt;. Your IAM user is now set up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeVDdX6jYhsOySEZ9zswuQsD5n7eLMp4tjTRlbm9JWW53ui9eRpc7nHrJ4h5SXmsjtOkz5Jg6GqBqU9cBlCZeeB5-HZTeN6Dq5jcTrPJPocIPB-SD1wBThyeiW26EEL-RM6OIA_9A%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeVDdX6jYhsOySEZ9zswuQsD5n7eLMp4tjTRlbm9JWW53ui9eRpc7nHrJ4h5SXmsjtOkz5Jg6GqBqU9cBlCZeeB5-HZTeN6Dq5jcTrPJPocIPB-SD1wBThyeiW26EEL-RM6OIA_9A%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an Access Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the new IAM user in place, the next step is to generate the Access Key ID and Secret Access Key, which will allow Terraform to authenticate and interact with AWS services.&lt;/p&gt;

&lt;p&gt;1.Navigate back to the &lt;strong&gt;IAM&lt;/strong&gt; service and click on &lt;strong&gt;Users&lt;/strong&gt; in the left sidebar.&lt;/p&gt;

&lt;p&gt;2.Select the newly created user (&lt;code&gt;terraform-admin&lt;/code&gt;) to open its details page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfOibGxXj32TUcAW_O3y4wZHOQl_465uubOY-jn0CfjqsZzDQLNl5SqnjvtfSMNe0T9zDwsbA4eVPshzM9gelrJlASMDp0wyJOrFhqAMzDgHufvNuKUkZZ5X4_TJ-LxCd3WkahQ0g%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfOibGxXj32TUcAW_O3y4wZHOQl_465uubOY-jn0CfjqsZzDQLNl5SqnjvtfSMNe0T9zDwsbA4eVPshzM9gelrJlASMDp0wyJOrFhqAMzDgHufvNuKUkZZ5X4_TJ-LxCd3WkahQ0g%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.Go to the &lt;strong&gt;Security Credentials&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdPJeG1HYEnh13M1YG1o-8o-tw_IJ-CHMZjlpb2Ru3hnD5-SP5x7NE-jnCfbTqnJ4Z0xMfHhW7nh2xGvSk61nUWKOlJRbI1s8uxDfyRckVhE3k4726eykqU0XldQQqz7lCLtyYA4Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdPJeG1HYEnh13M1YG1o-8o-tw_IJ-CHMZjlpb2Ru3hnD5-SP5x7NE-jnCfbTqnJ4Z0xMfHhW7nh2xGvSk61nUWKOlJRbI1s8uxDfyRckVhE3k4726eykqU0XldQQqz7lCLtyYA4Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.In the &lt;strong&gt;Access Keys&lt;/strong&gt; section, click on &lt;strong&gt;Create Access Key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;5.When prompted to select a use case, choose &lt;strong&gt;Command Line Interface (CLI)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcbe6dviZvt84XWe_dBI8nbX8TwRCXHsAvF06Kxgw-xxCCDxS10h8GSDX0eQU_1YwZgw3DwYDJB9oatlORREhKyyp6ZPceN-th1WwetrVib7L1LKqFl_evBQTdlWgk_FBJRREhxWA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcbe6dviZvt84XWe_dBI8nbX8TwRCXHsAvF06Kxgw-xxCCDxS10h8GSDX0eQU_1YwZgw3DwYDJB9oatlORREhKyyp6ZPceN-th1WwetrVib7L1LKqFl_evBQTdlWgk_FBJRREhxWA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6.Copy and securely store the &lt;strong&gt;Access Key ID&lt;/strong&gt; and &lt;strong&gt;Secret Access Key&lt;/strong&gt; displayed. After this step, AWS will not show the secret key again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure AWS CLI on Your Local Machine&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next step is to configure your local machine to authenticate with AWS using the AWS Command Line Interface (CLI). This allows Terraform to communicate with AWS services seamlessly.&lt;/p&gt;

&lt;p&gt;1.Open your terminal.&lt;/p&gt;

&lt;p&gt;2.Run the following command on your terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
aws configure

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Enter the following details when prompted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AWS Access Key ID: Paste the Access Key ID you copied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Secret Access Key: Paste the Secret Access Key you copied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Default region name: Enter &lt;code&gt;us-east-1&lt;/code&gt; (or your preferred AWS region).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Default output format: Enter &lt;code&gt;json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your local machine is now configured to authenticate with AWS using the AWS CLI, allowing Terraform to communicate seamlessly with AWS services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your Terraform configuration for EC2 deployment
&lt;/h2&gt;

&lt;p&gt;This section guides you through writing your Terraform configuration to provision an EC2 instance on AWS. You'll set up your project, define the AWS provider, create an SSH key pair, configure a security group, define your EC2 instance, and add useful outputs and variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1.Create a new directory for your Terraform project. Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;terraform-project &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;terraform-project

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generate an SSH Key Pair&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To securely connect to your EC2 instance using SSH, you need an SSH key pair. You'll generate this on your local machine and then upload the public key to AWS through Terraform.&lt;/p&gt;

&lt;p&gt;1.Open your terminal.&lt;/p&gt;

&lt;p&gt;2.Generate a new SSH key pair by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/my-ec2-key &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"my-ec2-key"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates an &lt;code&gt;Ed25519&lt;/code&gt; key pair, which is a modern and highly secure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-f ~/.ssh/my-ec2-key&lt;/code&gt; flag specifies the file path and name for your &lt;strong&gt;private key&lt;/strong&gt; (&lt;code&gt;my-ec2-key&lt;/code&gt;) and its corresponding &lt;strong&gt;public key&lt;/strong&gt; (&lt;code&gt;my-ec2-key.pub&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-C "my-ec2-key"&lt;/code&gt; flag adds a descriptive comment to the key, making it easier to identify later.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3.When prompted for a passphrase, you can press &lt;strong&gt;Enter&lt;/strong&gt; to leave it blank for simplicity in this tutorial. However, in a production environment, always use a strong passphrase to protect your private key. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcJ_-n64VSfrjcsZEOhwpJohf_5I9npFJfBnXrHXcSS1w6-3DMhHq2-G7hYFt5kgh76hTIw_v_5SB3bQuCCHYZFZZUiG6VZEyoWcROghK8VXvAZ25B-e4OpIKWaJTjHPBxjSxwnAA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcJ_-n64VSfrjcsZEOhwpJohf_5I9npFJfBnXrHXcSS1w6-3DMhHq2-G7hYFt5kgh76hTIw_v_5SB3bQuCCHYZFZZUiG6VZEyoWcROghK8VXvAZ25B-e4OpIKWaJTjHPBxjSxwnAA%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1536" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This command generates two files in your &lt;code&gt;~/.ssh&lt;/code&gt; directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;my-ec2-key&lt;/code&gt;: This is your &lt;strong&gt;private key&lt;/strong&gt;. Keep this file absolutely secure and never share it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;my-ec2-key.pub&lt;/code&gt;: This is your &lt;strong&gt;public key&lt;/strong&gt;. You will upload the contents of this file to AWS through Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Defining Your Terraform Configuration Files
&lt;/h3&gt;

&lt;p&gt;To provision the EC2 instance, you'll organise your Terraform configuration into several core files, each serving a specific purpose:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define Terraform Variables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Variables make your Terraform configurations flexible and reusable. Define them upfront to easily customise your deployment without altering the main resource definitions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new file named &lt;code&gt;variables.tf&lt;/code&gt; in your &lt;code&gt;terraform-project&lt;/code&gt; directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following content to &lt;code&gt;variables.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
variable  "aws_region" {

 description =  "The AWS region to deploy resources in."

 type =  string

 default =  "us-east-1"  # Set your default region here

}

variable  "instance_type" {

 description =  "The type of EC2 instance."

 type =  string

 default =  "t2.micro"  # Set your default instance type here

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;aws_region&lt;/code&gt;: Specifies the AWS region for resource deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;instance_type&lt;/code&gt;: Defines the type of EC2 instance to launch.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Define the AWS Provider and Key Pair&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This file will configure Terraform to interact with AWS and upload your generated public SSH key to the AWS Key Pairs service.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new file named &lt;code&gt;provider.tf&lt;/code&gt; in your &lt;code&gt;terraform-project&lt;/code&gt; directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following content to &lt;code&gt;provider.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;

&lt;span class="err"&gt;       &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.0"&lt;/span&gt;  &lt;span class="c1"&gt;# Specify a compatible version (e.g., 5.x)&lt;/span&gt;

&lt;span class="err"&gt;     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_region&lt;/span&gt;  &lt;span class="c1"&gt;# Use the region defined in variables.tf&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_key_pair"&lt;/span&gt; &lt;span class="s2"&gt;"ec2_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-ec2-key"&lt;/span&gt;  &lt;span class="c1"&gt;# This is the name AWS will use for your key&lt;/span&gt;

&lt;span class="err"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;public_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"~/.ssh/my-ec2-key.pub"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Path to your public key file&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;terraform&lt;/code&gt; block: Declares the required AWS provider and its version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;provider "aws"&lt;/code&gt; block: Configures the AWS provider, using the aws_region variable for consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resource "aws_key_pair"&lt;/code&gt;: This resource uploads the content of your my-ec2-key.pub file to AWS, making it available for new EC2 instances. The key_name is how you'll refer to it in AWS.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create the Security Group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A security group acts as a virtual firewall for your EC2 instance, controlling both inbound (ingress) and outbound (egress) network traffic.&lt;/p&gt;

&lt;p&gt;1.Create a new file &lt;code&gt;security_group.tf&lt;/code&gt; in your &lt;code&gt;terraform-project&lt;/code&gt; directory.&lt;br&gt;
2.Add the following configuration to &lt;code&gt;security_group.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="k"&gt;resource&lt;/span&gt;  &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt;  &lt;span class="s2"&gt;"ec2_security_group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-ec2-sg"&lt;/span&gt;

&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH and HTTP access for Terraform EC2 instance"&lt;/span&gt;

&lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH from my IP"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow HTTP from anywhere"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;  &lt;span class="c1"&gt;# Allow all outbound protocols&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s2"&gt;"Terraform-EC2-SecurityGroup"&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ingress&lt;/code&gt; blocks: Define inbound rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;egress&lt;/code&gt; block: Allows all outbound traffic from the instance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;tags&lt;/code&gt;: Apply a tag for easy identification in the AWS console.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Define the EC2 Instance and AMI Data Source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;main.tf&lt;/code&gt; file will contain the core definition of your EC2 instance. It also includes a data source to dynamically fetch the latest Ubuntu AMI.&lt;/p&gt;

&lt;p&gt;1.Create a new file &lt;code&gt;main.tf&lt;/code&gt; in your &lt;code&gt;terraform-project&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;2.Add the following content to &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# Data source to get the latest Ubuntu AMI&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt;  &lt;span class="s2"&gt;"aws_ami"&lt;/span&gt;  &lt;span class="s2"&gt;"latest_ubuntu_ami"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nx"&gt;most_recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"virtualization-type"&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hvm"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;owners&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"099720109477"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Canonical's AWS account ID&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Define the EC2 instance resource&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt;  &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt;  &lt;span class="s2"&gt;"my_first_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latest_ubuntu_ami&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;  &lt;span class="c1"&gt;# Use the dynamically fetched AMI ID&lt;/span&gt;

&lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="c1"&gt;# Use the instance type defined in variables.tf&lt;/span&gt;

&lt;span class="nx"&gt;key_name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_key_pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key_name&lt;/span&gt; &lt;span class="c1"&gt;# Reference the key pair created in provider.tf&lt;/span&gt;

&lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Attach the security group&lt;/span&gt;

&lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="s2"&gt;"MyFirstTerraformEC2"&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;data "aws_ami"&lt;/code&gt;: This data source automatically retrieves the ID of the most recent Ubuntu 22.04 LTS AMI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;resource "aws_instance"&lt;/code&gt;: This block defines the EC2 instance itself, using the ami from the data source, instance_type from variables, and referencing the key_name and security_group created in their respective files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Define the Outputs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Outputs are useful for displaying important information about your deployed resources after Terraform applies the configuration.&lt;/p&gt;

&lt;p&gt;1.Create a new file &lt;code&gt;outputs.tf&lt;/code&gt; in your &lt;code&gt;terraform-project&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;2.Add the following content to outputs.tf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"instance_public_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Public IP address of the EC2 instance."&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_first_ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These outputs will display the public &lt;code&gt;IP address&lt;/code&gt; of your EC2 instance once it's provisioned, making it easy to connect to your instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialising and Applying Your Configuration
&lt;/h2&gt;

&lt;p&gt;After writing your Terraform configuration files, the next step is to initialise your working directory, validate your code, review the execution plan, and finally apply the changes to provision your AWS resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialise Your Terraform Project&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;terraform init&lt;/code&gt; command prepares your project's working directory by downloading necessary provider plugins and setting up the backend for state management. Run this command first whenever you start a new Terraform project or clone an existing one.&lt;/p&gt;

&lt;p&gt;1.Open your terminal and navigate to your project directory if you are not already there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
cd terraform-project

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Run the initialisation command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
terraform init

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcuEfb5QhoFxLu2CPz22vyorNTkln6-vjeZMkRi5LfTT073TGm8Q0BNwzXFmLOtKhJCIaAtjD-nceZtaTFjVqZWBb_N9JKnY33GqDY_7jqQo2doz1qcm3pJWj7__aHpPR9_Rmes%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcuEfb5QhoFxLu2CPz22vyorNTkln6-vjeZMkRi5LfTT073TGm8Q0BNwzXFmLOtKhJCIaAtjD-nceZtaTFjVqZWBb_N9JKnY33GqDY_7jqQo2doz1qcm3pJWj7__aHpPR9_Rmes%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate Your Terraform Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Validating your Terraform code before attempting to create resources is a good practice. The &lt;code&gt;terraform validate&lt;/code&gt; command checks your configuration for syntax errors and internal consistency.&lt;/p&gt;

&lt;p&gt;To validate your configuration, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
terraform validate

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfRrCKdf7BZa4-ghnPXxOwSfPGVQFlJ9BeVc1LDvO_4LnTNuWZ9j-4fPMTpY66gmtFAobWdoCfVGu-fal0nS6GFpuZqrQ7lO8h_QjBCEVk3LLtir8dIrcN9B_HKcARzSiW4UmR6Qg%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfRrCKdf7BZa4-ghnPXxOwSfPGVQFlJ9BeVc1LDvO_4LnTNuWZ9j-4fPMTpY66gmtFAobWdoCfVGu-fal0nS6GFpuZqrQ7lO8h_QjBCEVk3LLtir8dIrcN9B_HKcARzSiW4UmR6Qg%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="896" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there are any errors, Terraform will provide detailed messages to help you fix them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review the Execution Plan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before making any changes to your AWS environment, always generate and review an execution plan using &lt;code&gt;terraform plan&lt;/code&gt;. This command shows you exactly what Terraform will create, modify, or destroy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
terraform plan

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform compares your desired state (defined in your &lt;code&gt;.tf&lt;/code&gt; files) with the current state of your AWS resources and proposes a set of changes to achieve the desired state.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcm2J9Faz9QFhJ-KG_lEKiBXxt_9ZmWOaecZgQS2UZlKgxBJKxpzBdXQiG2bxZJ4NyxVONdpnKgxWPRGqVlp2Wsk_SyA_tZqL2xDck-PCj2sGZK7jP1BXegk4AJzrqSAzACdaCV2Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcm2J9Faz9QFhJ-KG_lEKiBXxt_9ZmWOaecZgQS2UZlKgxBJKxpzBdXQiG2bxZJ4NyxVONdpnKgxWPRGqVlp2Wsk_SyA_tZqL2xDck-PCj2sGZK7jP1BXegk4AJzrqSAzACdaCV2Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1600" height="858"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apply Your Terraform Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you are satisfied with the execution plan, use &lt;code&gt;terraform apply&lt;/code&gt; to provision the resources in your AWS account.&lt;/p&gt;

&lt;p&gt;Apply the configuration to provision resources, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command executes the actions outlined in the &lt;code&gt;terraform plan&lt;/code&gt;. Terraform will interact with the AWS API to create, update, or delete resources as necessary.&lt;/p&gt;

&lt;p&gt;[&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcQ1tdeS-fWnilHo0tp0-qYwde1LxhwcfSf6IZZS0Le7pMB8spxTAGvsaSls5lP6TkrarREyxcE1x1W1jDR5gQvsz6OZCg3awoRtDdm-OkzHTCtZsynodjvCv5PpCctXrnr08rmmg%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcQ1tdeS-fWnilHo0tp0-qYwde1LxhwcfSf6IZZS0Le7pMB8spxTAGvsaSls5lP6TkrarREyxcE1x1W1jDR5gQvsz6OZCg3awoRtDdm-OkzHTCtZsynodjvCv5PpCctXrnr08rmmg%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1438" height="1046"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying Your EC2 Instance
&lt;/h2&gt;

&lt;p&gt;Confirm your EC2 instance is active in your AWS account following a successful terraform apply. This tutorial focuses on verification using the AWS Console, though the AWS CLI is also an option.&lt;/p&gt;

&lt;p&gt;The AWS Management Console provides a visual way to confirm your instance's status.&lt;/p&gt;

&lt;p&gt;1.Log in to your AWS Console.&lt;/p&gt;

&lt;p&gt;2.In the search bar, type &lt;strong&gt;EC2&lt;/strong&gt; and select the &lt;strong&gt;EC2&lt;/strong&gt; service when it appears. This will take you to the EC2 Dashboard.&lt;/p&gt;

&lt;p&gt;3.On the left-hand navigation pane, under &lt;strong&gt;Instances&lt;/strong&gt;, click on &lt;strong&gt;Instances&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;4.Locate your newly created EC2 instance. You should see an instance with the &lt;strong&gt;Name&lt;/strong&gt; tag &lt;code&gt;MyFirstTerraformEC2&lt;/code&gt; (or whatever name you assigned in your &lt;code&gt;main.tf&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdHGtIZyXRES__BsgK_0DOv1YtgeT1pee2sGws1pVREmPeywV-ScXkcCisiI7G3F3A8cEbVuF6XfJh5edo95ypet2iDyLUfO5H3j05qiES6DY-oHXrURtkzFxlMAw_ExfwNYfdT5Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdHGtIZyXRES__BsgK_0DOv1YtgeT1pee2sGws1pVREmPeywV-ScXkcCisiI7G3F3A8cEbVuF6XfJh5edo95ypet2iDyLUfO5H3j05qiES6DY-oHXrURtkzFxlMAw_ExfwNYfdT5Q%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1600" height="952"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;5.Check the &lt;strong&gt;Instance state&lt;/strong&gt; column. It should show &lt;strong&gt;Running&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;6.Select your instance and review its details in the lower pane, including its Public IPv4 address, Instance ID, and associated Security Groups, to confirm everything matches your Terraform configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up your provisioned resources
&lt;/h2&gt;

&lt;p&gt;After you've finished experimenting with your EC2 instance, it's crucial to clean up the resources you've provisioned. This prevents unnecessary AWS charges and keeps your account tidy. Terraform makes this process simple with a single command.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;terraform destroy&lt;/code&gt; command removes all resources defined in your current Terraform configuration.&lt;/p&gt;

&lt;p&gt;1.Open your terminal.&lt;/p&gt;

&lt;p&gt;2.Navigate to your project directory if you are not already there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nb"&gt;cd &lt;/span&gt;terraform-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Initiate the destruction process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform will calculate all the resources that it previously created and are still managed by your configuration, and it will propose to destroy them. This includes your EC2 instance, security group, and the uploaded key pair.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcKx4wpA0zhohJH50ZNg04XyQii3a5I2Snlp1CC_hgQOZ8FPTAm0w8vJWdYLzSM-rFTw0Jyg2t2-DIKnI49IOpCn26Mg2ko6g8RB8IChs-AGob7z6uvmsRU08aXUfFbvneefdQdWw%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcKx4wpA0zhohJH50ZNg04XyQii3a5I2Snlp1CC_hgQOZ8FPTAm0w8vJWdYLzSM-rFTw0Jyg2t2-DIKnI49IOpCn26Mg2ko6g8RB8IChs-AGob7z6uvmsRU08aXUfFbvneefdQdWw%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1388" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the instance has been terminated when verified on AWS Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXd3n8BzG4DlmbdlEg77v2tTWUdAfKPB2QuC5Q9wRRcBHRXQV0OLqArI4jZOe9khInZGtJovBJ8UoV5EoI2wuuDuMpHJE-N5P_eWWZ4yUbjkTaqeEwDeKYwo__rBZRSl9Y64KAr6Uw%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXd3n8BzG4DlmbdlEg77v2tTWUdAfKPB2QuC5Q9wRRcBHRXQV0OLqArI4jZOe9khInZGtJovBJ8UoV5EoI2wuuDuMpHJE-N5P_eWWZ4yUbjkTaqeEwDeKYwo__rBZRSl9Y64KAr6Uw%3Fkey%3DWxo0fkVV6bvJ9rvNYdaJMg" width="1600" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices for working with Terraform 
&lt;/h2&gt;

&lt;p&gt;You've successfully provisioned and destroyed your first EC2 instance with Terraform! As you continue your Infrastructure as Code journey, consider these best practices and areas for further exploration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version Control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Always store your Terraform configuration files (.tf files) in a version control system like Git. This allows you to track changes, collaborate with others, and easily revert to previous versions if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git Ignore Sensitive Files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prevent sensitive information and Terraform's internal working files from being committed to your Git repository. Create a .gitignore file in your project root and add the following entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore&lt;/span&gt;

.terraform/

&lt;span class="k"&gt;*&lt;/span&gt;.tfstate

&lt;span class="k"&gt;*&lt;/span&gt;.tfstate.backup

.terraform.lock.hcl

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.terraform&lt;/code&gt; contains downloaded provider plugins and other internal Terraform files. You don't need to commit these.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;*.tfstate&lt;/code&gt; is your Terraform state file. If you're not using remote state (which is recommended for teams), keep this out of version control as it can contain sensitive information and is crucial for Terraform's operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;*.tfstate.backup&lt;/code&gt; backup state files don't need to be pushed as well.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;.terraform.lock.hcl&lt;/code&gt; locks provider versions to ensure consistent builds, but it's typically fine to commit. The outline asks to ignore it if not using remote state, so I've included it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security is paramount in cloud environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Never commit sensitive data directly: Avoid hardcoding AWS access keys or other secrets directly into your &lt;code&gt;.tf&lt;/code&gt; files, especially if your repository is public or shared. Instead, use secure methods like AWS environment variables (as leveraged in this tutorial), AWS Secrets Manager, or a dedicated secret management tool like HashiCorp Vault.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Principle of Least Privilege: Always grant Terraform (or the IAM user/role it uses) only the minimum necessary permissions to perform its tasks. For this tutorial, we used &lt;code&gt;AdministratorAccess&lt;/code&gt; for simplicity, but for production environments, define specific IAM policies that restrict actions to only the required resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Modularization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As your infrastructure grows, your Terraform configurations can become large and complex. Consider breaking down your configurations into reusable modules. Modules allow you to encapsulate and reuse common infrastructure patterns (e.g., a standard VPC setup, a common EC2 instance configuration) across different projects or environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For team collaboration and production deployments, use remote state backends (e.g., Amazon S3, HashiCorp Cloud Platform, Terraform Cloud). Remote state safely stores your &lt;code&gt;terraform.tfstate&lt;/code&gt; file in a shared, versioned, and often encrypted location. This enables multiple team members to work on the same infrastructure simultaneously without state conflicts and provides a reliable source of truth for your infrastructure's current state.&lt;/p&gt;

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

&lt;p&gt;In this tutorial, you have successfully set up your AWS environment, written your first Terraform configuration, provisioned an EC2 instance, checked that it was deployed, and cleaned up the resources. You've experienced the core Terraform workflow: initialising your project, planning changes, applying them to create infrastructure, and finally, destroying it to avoid unintended costs. This exercise has equipped you with the building blocks for managing AWS resources using Infrastructure as Code (IaC).&lt;/p&gt;

&lt;p&gt;The steps covered, from IAM user creation and SSH key management to defining resources like security groups and EC2 instances, form the bedrock of more complex deployments. As you continue to explore Terraform, remember the best practices discussed: leverage version control, secure your sensitive data, adhere to the principle of least privilege, and consider modularising your code and using remote state for collaborative or production environments&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automating User Creation and Management with Bash Scripting: A Detailed Guide.</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Fri, 05 Jul 2024 11:31:25 +0000</pubDate>
      <link>https://dev.to/kalio/automating-user-creation-and-management-with-bash-scripting-a-detailed-guide-pmh</link>
      <guid>https://dev.to/kalio/automating-user-creation-and-management-with-bash-scripting-a-detailed-guide-pmh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;Linux, being a multi-user system, empowers administrators to create users and groups, each with specific permissions. When users are assigned to groups, they acquire the group's permissions. Effective user and group management is essential for system administration, from small personal servers to extensive enterprise networks. Streamlining the user creation process is crucial for ensuring security, organization, and efficiency, and Bash scripting provides a powerful means of automating these tasks.&lt;/p&gt;

&lt;p&gt;This guide will walk you through creating a Bash script for managing user accounts on a Linux system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Your company has employed many new developers. As a SysOps engineer, it's your responsibility to efficiently manage user accounts and their associated permissions. To streamline this process, you are tasked with writing a Bash script that automates the setup and configuration of new user accounts based on predefined criteria.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User and Group Definition:&lt;/strong&gt; Users and their groups are defined in a text file that will be supplied to the script as an argument.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Home Directory Creation:&lt;/strong&gt; A dedicated home directory should be created for each user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure Password Storage:&lt;/strong&gt; User passwords should be stored securely in a file with path &lt;code&gt;/car/secure/user_passwords.txt&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logging Actions:&lt;/strong&gt; Logs of all actions should be logged to &lt;code&gt;/var/log/user_management.log&lt;/code&gt; for auditing and troubleshooting purposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File Access Control:&lt;/strong&gt; Only the owner, in this case root, should be able to access the &lt;code&gt;user_password.txt file&lt;/code&gt;, ensuring that unauthorised access is prevented.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Error Handling:&lt;/strong&gt; Errors should be gracefully handled&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To begin this tutorial, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
A Linux system with administrative access.&lt;/li&gt;
&lt;li&gt;
Basic familiarity with Linux commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Script:
&lt;/h2&gt;

&lt;p&gt;The script, &lt;code&gt;create_user.sh&lt;/code&gt;, automates user account creation, password encryption, group assignment, and logging activities on a Linux system. The goal is to streamline user management and ensure that all actions are securely recorded and auditable. Below is a detailed breakdown of each section of the script, including its purpose and functionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shebang.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; #!/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This specifies the type of interpreter script will be run with. Since it is a "bash" script, it should be run with the Bourne Again Shell (Bash) interpreter. Also, some commands in the script may not be interpreted correctly outside of Bash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining File Paths and Variables&lt;/strong&gt;&lt;br&gt;
Next, create some environment variables that will hold the text_file.txt, the logs file path (var/log/user_management.log), and password file path (/var/secure/user_passwords.csv).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE_DIRECTORY="/var/secure"
PASSWORD_FILE="/var/secure/user_passwords.txt"
PASSWORD_ENCRYPTION_KEY="secure-all-things"
USERS_FILE=$1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;LOG_FILE&lt;/code&gt;: specifies where the log entries will be stored. Logging is crucial for tracking actions and diagnosing issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PASSWORD_FILE_DIRECTORY&lt;/code&gt;: defines the directory for storing user passwords securely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PASSWORD_FILE&lt;/code&gt;: is the file where encrypted passwords will be saved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;PASSWORD_ENCRYPTION_KEY&lt;/code&gt;: is used to encrypt user passwords.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;USERS_FILE&lt;/code&gt;: takes the first script argument, which should be the path to a file containing user data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, create this file  path and give them the right permission using the command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch $LOG_FILE
mkdir -p /var/secure
chmod 700 /var/secure
touch $PASSWORD_FILE
chmod 600 $PASSWORD_FILE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details of the command in the codeblock are:&lt;br&gt;
&lt;code&gt;touch $LOG_FILE&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;touch&lt;/code&gt;: This command is used to create an empty file or update the access and modification times of an existing file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$LOG_FILE&lt;/code&gt;: This variable holds the path("/var/log/user_management.log") to the log file.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;mkdir -p /var/secure&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;mkdir&lt;/code&gt;: This command is used to create directories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-p&lt;/code&gt;: This option allows the creation of parent directories as needed. If the directory already exists, it will not return an error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/var/secure&lt;/code&gt;: This is the directory path to be created.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;chmod 700 /var/secure&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;chmod&lt;/code&gt;: This command is used to change the permissions of files or directories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;700&lt;/code&gt;: This permission setting means:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;7&lt;/code&gt;: The owner has read, write, and execute permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;: The group has no permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;: Others have no permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/var/secure&lt;/code&gt;: The directory whose permissions are being modified.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;chmod 600 $PASSWORD_FILE&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;chmod&lt;/code&gt;: This command changes the permissions of a file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;600&lt;/code&gt;: This permission setting means:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;6&lt;/code&gt;: The owner has read and write permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;: The group has no permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;: Others have no permissions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Checking for Sudo Privileges&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ "$(id -u)" != "0" ]; then  
    echo "This script must be run with sudo. Exiting..."   
    exit 1
 fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code ensures the script runs with root privileges by checking if the Effective User ID (EUID) is zero. In Linux, an EUID of &lt;code&gt;0&lt;/code&gt; corresponds to the root user. If the script is not executed with administrative rights, it exits with an error message. This is because the script will perform actions that require elevated privileges, such as creating users and groups, setting passwords, creating files, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validating Arguments&lt;/strong&gt;&lt;br&gt;
Next to ensure that the script is properly executed with an input file provided as an argument, this conditional check terminates the script if no argument is detected. Using &lt;code&gt;$#&lt;/code&gt;to represent the number of arguments passed when executing the script, it validates whether no argument ($# equals zero) or more than one argument ($# is greater than or equal to 2) is given. If either condition is met, an error message is displayed, halting the script's execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ $# -eq 0 ]; then   
   echo "No file path provided."   
   echo "Usage: $0 &amp;lt;user-data-file-path&amp;gt;"   
   exit 1 
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also to verify if the provided file exists. If the file does not exist, the script exits with an error message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ ! -e "$USERS_FILE" ]; then
  echo "The provided user's data file does not exist: $USERS_FILE"
  exit 1
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Function Definitions&lt;/strong&gt;&lt;br&gt;
Next to ensure reusability and modularity in your code, copy and paste the several utility functions to your code&lt;br&gt;
&lt;strong&gt;Check for Installed Package:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;is_package_installed() {
  dpkg -s "$1" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function checks if a package is installed on your machine  using &lt;code&gt;dpkg&lt;/code&gt;. It suppresses output and returns a status code.&lt;br&gt;
&lt;strong&gt;Encrypt Password:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encrypt_password() {
  echo "$1" | openssl enc -aes-256-cbc -pbkdf2 -base64 -pass pass:"$2"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commnad uses openssl to encrypt a password. The password is encrypted using &lt;code&gt;AES-256-CBC&lt;/code&gt; and a provided encryption key.&lt;br&gt;
&lt;strong&gt;Set Bash as Default Shell:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_bash_default_shell() {
  local user="$1"
  sudo chsh -s /bin/bash "$user"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function sets Bash as the default shell for the specified user.&lt;br&gt;
&lt;strong&gt;Installing Required Packages:&lt;/strong&gt;&lt;br&gt;
To ensure you have the necessary packages to run the script, add the code-block&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ! is_package_installed openssl; then
  echo "openssl is not installed. Installing..."
  sudo apt-get update
  sudo apt-get install -y openssl
fi

if ! is_package_installed pwgen; then
  echo "pwgen is not installed. Installing..."
  sudo apt-get update
  sudo apt-get install -y pwgen
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the command above you have defined variables for the file paths corresponding to the log file, password file, and input file. Additionally, you have ensured that the script is being executed with superuser privileges.&lt;/p&gt;

&lt;p&gt;The subsequent step involves iterating through each line in the input text file, separating these lines by usernames and groups, and then using the Bash script to create the users and assign them to their respective groups.&lt;br&gt;
To set up these users and groups, incorporate the following commands into your &lt;code&gt;create_users.sh&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for line in "${lines[@]}"; do
    # Remove leading and trailing whitespaces
    line=$(echo "$line" | xargs)

    # Split line by ';' and store the second part
    IFS=';' read -r user groups &amp;lt;&amp;lt;&amp;lt; "$line"

    # Remove leading and trailing whitespaces from the second part
    groups=$(echo "$groups" | xargs)

    # Create a variable groupsArray that is an array from spliting the groups of each user
    IFS=',' read -ra groupsArray &amp;lt;&amp;lt;&amp;lt; "$groups"

    # Check if user exists
    if id "$user" &amp;amp;&amp;gt;/dev/null; then
        echo "User $user already exists. Skipping creation."
        continue
    fi

    # Generate a 6-character password using pwgen
    password=$(pwgen -sBv1 6 1)

    # Encrypt the password before storing it
    encrypted_password=$(encrypt_password "$password" "$PASSWORD_ENCRYPTION_KEY")

    # Store the encrypted password in the file
    echo "$user:$encrypted_password" &amp;gt;&amp;gt; "$PASSWORD_FILE"

    # Create the user with the generated password
    sudo useradd -m -p $(openssl passwd -6 "$password") "$user"

    # Set Bash as the default shell
    set_bash_default_shell "$user"

    # loop over each group in the groups array
    for group in "${groupsArray[@]}"; do
        group=$(echo "$group" | xargs)

        # Check if group exists, if not, create it
        if ! grep -q "^$group:" /etc/group; then
            sudo groupadd "$group"
            echo "Created group $group"
        fi

        # Add user to the group
        sudo usermod -aG "$group" "$user"
        echo "Added $user to $group"
    done

    echo "User $user created and password stored securely"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above does the following:&lt;br&gt;
&lt;strong&gt;Iterating Through Lines in the File&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for line in "${lines[@]}"; do

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command initiates a loop that iterates over each line in the &lt;code&gt;lines&lt;/code&gt; array. Each element of the array &lt;code&gt;lines&lt;/code&gt;, representing a line from the input file, is processed one by one and assigned to the &lt;code&gt;line&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Removing Leading and Trailing Whitespace&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;line=$(echo "$line" | xargs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;echo "$line" | xargs&lt;/code&gt; removes any leading and trailing whitespace from the current line. This ensures that any extra spaces at the beginning or end of the line are trimmed off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Splitting the Line into User and Groups&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IFS=';' read -r user groups &amp;lt;&amp;lt;&amp;lt; "$line"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line splits the &lt;code&gt;line&lt;/code&gt;variable into two parts based on the ; delimiter. The first part is assigned to &lt;code&gt;user&lt;/code&gt;, and the second part is assigned to&lt;code&gt;groups. IFS=';'&lt;/code&gt;sets the Internal Field Separator to ;, which defines how the line is split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trimming Whitespace from Groups&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;groups=$(echo "$groups" | xargs)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command removes any leading and trailing whitespace from the &lt;code&gt;groups&lt;/code&gt;variable. This is necessary to ensure that the group names are clean and free from extra spaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Splitting Groups into an Array&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IFS=',' read -ra groupsArray &amp;lt;&amp;lt;&amp;lt; "$groups"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;IFS=','&lt;/code&gt; sets the Internal Field Separator to &lt;code&gt;,&lt;/code&gt;. The read &lt;code&gt;-ra&lt;/code&gt; groupsArray splits the groups string into an array groupsArray based on commas, with each element representing a group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking if User Already Exists&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if id "$user" &amp;amp;&amp;gt;/dev/null; then
    echo "User $user already exists. Skipping creation."
    continue
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This block checks if a user with the given &lt;code&gt;user&lt;/code&gt; name already exists. The &lt;code&gt;id "$user" &amp;amp;&amp;gt;/dev/null&lt;/code&gt; command attempts to fetch user details, and if successful (i.e., the user exists), it outputs nothing to &lt;code&gt;/dev/null&lt;/code&gt;. If the user exists, a message is printed, and the &lt;code&gt;continue&lt;/code&gt; command skips the rest of the loop for this user and moves to the next iteration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating a 6-Character Password&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;password=$(pwgen -sBv1 6 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates a secure, random 6-character password using the &lt;code&gt;pwgen&lt;/code&gt; tool. The &lt;code&gt;-s&lt;/code&gt; flag ensures a secure password, &lt;code&gt;-B&lt;/code&gt; avoids ambiguous characters, and -v1 specifies the length and quantity (1 password).&lt;br&gt;
&lt;strong&gt;Encrypting the Password&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encrypted_password=$(encrypt_password "$password" "$PASSWORD_ENCRYPTION_KEY")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line calls the &lt;code&gt;encrypt_password&lt;/code&gt; function to encrypt the generated password. The encrypted password is stored in the &lt;code&gt;encrypted_password&lt;/code&gt;variable. The encryption key used is stored in the &lt;code&gt;PASSWORD_ENCRYPTION_KEY&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storing the Encrypted Password&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "$user:$encrypted_password" &amp;gt;&amp;gt; "$PASSWORD_FILE"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command appends the username and encrypted password to the &lt;code&gt;PASSWORD_FILE.&lt;/code&gt; The &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; operator ensures that the data is added to the end of the file without overwriting existing content.&lt;br&gt;
&lt;strong&gt;Creating the User with the Generated Password&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo useradd -m -p $(openssl passwd -6 "$password") "$user"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This line creates a new user with the username stored in user. The &lt;code&gt;-m&lt;/code&gt; option creates the user's home directory. The password is hashed using &lt;code&gt;openssl passwd -6&lt;/code&gt; with the generated password, ensuring it is securely stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting Bash as the Default Shell&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_bash_default_shell "$user"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function call sets Bash as the default shell for the new user. The &lt;code&gt;set_bash_default_shell&lt;/code&gt; function is assumed to be defined elsewhere in the script to change the user's shell to Bash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding User to Specified Groups&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for group in "${groupsArray[@]}"; do
    group=$(echo "$group" | xargs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loop iterates through each group in the groupsArray array. Each group is cleaned of leading and trailing whitespace.&lt;br&gt;
&lt;strong&gt;Checking and Creating Group if Necessary&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ! grep -q "^$group:" /etc/group; then
    sudo groupadd "$group"
    echo "Created group $group"
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each group, this block checks if the group exists in the &lt;code&gt;/etc/group&lt;/code&gt; file. If not, the &lt;code&gt;groupadd&lt;/code&gt; command creates the group, and a confirmation message is printed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding User to the Group&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG "$group" "$user"
echo "Added $user to $group"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command adds the user to the specified group using &lt;code&gt;usermod -aG&lt;/code&gt;. The &lt;code&gt;-aG&lt;/code&gt; option appends the user to the supplementary group without affecting membership in other groups. A message is printed confirming the addition.&lt;/p&gt;

&lt;p&gt;With this, you’ve successfully developed a script that efficiently handles user and group management on your system. You can find the complete script on the &lt;a href="https://github.com/kalio007/auto-user-management" rel="noopener noreferrer"&gt;Github Repo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying Script Functionality
&lt;/h3&gt;

&lt;p&gt;To verify that your script is functioning correctly, execute it in a terminal that supports Linux commands. Use the following command in your terminal to run the script:&lt;/p&gt;

&lt;p&gt;Firstly, make your script executable, run the command below,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x create_users.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then, run the script,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./create_user.sh ./text_file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;This article details the procedure for automating the creation of users and groups through a script. It incorporates several assumptions and compromises to balance security with usability. As an administrator, you can now leverage this script to streamline user onboarding within your organisation.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>sysops</category>
      <category>bash</category>
      <category>linux</category>
    </item>
    <item>
      <title>Deploying a Static Website on AWS EC2 with Nginx: A Comprehensive Guide</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Tue, 02 Jul 2024 08:16:43 +0000</pubDate>
      <link>https://dev.to/kalio/deploying-a-static-website-on-aws-ec2-with-nginx-a-comprehensive-guide-1953</link>
      <guid>https://dev.to/kalio/deploying-a-static-website-on-aws-ec2-with-nginx-a-comprehensive-guide-1953</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS (Amazon Web Services)&lt;/a&gt; is a comprehensive cloud computing platform provided by Amazon, offering a wide range of services including computing power, storage, and databases. It enables businesses and developers to access and use scalable and cost-effective cloud resources on-demand. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is AWS EC2?&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://aws.amazon.com/ec2/" rel="noopener noreferrer"&gt;Amazon Elastic Compute Cloud (EC2)&lt;/a&gt; is a core service within AWS that provides on-demand virtual machines (instances). You can select from a diverse range of instance types to match your specific computational needs. AWS EC2 enables elastic scaling, allowing you to adjust compute power up or down based on requirements. &lt;/p&gt;

&lt;p&gt;This guide will concentrate on AWS EC2, demonstrating how to use its virtual machine features to set up a server environment tailored for hosting your static website with Nginx web server.&lt;/p&gt;

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

&lt;p&gt;To begin this tutorial, the following are good to have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; To complete this project you should have a fully verified AWS account, to get on visit &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Also a good to have would be basic knowledge of linux commands.&lt;/li&gt;
&lt;li&gt; Basic understanding of HTML and CSS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete the project above i will walk you through  as step by step procedure:&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating an AWS EC2 instance:
&lt;/h3&gt;

&lt;p&gt;Firstly, sign into your AWS console and click on the EC2 under the Compute section:&lt;/p&gt;

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

&lt;p&gt;Next, to launch an EC2 instance click on the &lt;code&gt;Launch Instance&lt;/code&gt; button &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Name and tags&lt;/strong&gt; &lt;br&gt;
Next, give your EC2 Instance a desired name,  &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Application and OS images&lt;/strong&gt;&lt;br&gt;
For this project select Ubuntu as the application image for the instance.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Amazon Machine Image (AMI)&lt;/strong&gt;&lt;br&gt;
Select the Ubuntu Server and free tier, you don’t want to spend money when it can be free.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Key-Value Pair:&lt;/strong&gt; Key-value pairs are used in instance metadata to securely configure access, retrieve instance-specific information, and automate initial setup tasks.&lt;br&gt;
Next, create a new key pair and choose the .pem format.&lt;/p&gt;

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

&lt;p&gt;Give the Key pair a name and ensure the fields are selected, this should download a file on your browser.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Network Settings:&lt;/strong&gt; &lt;br&gt;
Network settings configure firewall rules to control the traffic to your instance. In this section, enable options for Allow SSH traffic from Anywhere, Allow HTTPS traffic from the Internet, and Allow HTTP traffic from the Internet. &lt;br&gt;
Click on the launch Instance button to continue.&lt;/p&gt;

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

&lt;p&gt;After a successful launch, you should see the screen below &lt;/p&gt;

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

&lt;p&gt;click on the instances button on the left sidebar to view your running instance, select the checkbox of your instance to see more details on the instance as it’s displayed below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx76ktcfuivo6pfl6j8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx76ktcfuivo6pfl6j8n.png" alt=" " width="800" height="466"&gt;&lt;/a&gt;&lt;br&gt;
To  use your  instance, click on the &lt;code&gt;Connect&lt;/code&gt; button, this should open the screen below, &lt;/p&gt;

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

&lt;p&gt;Click  the orange connect button to launch the terminal of your EC2 instance Virtual machine.&lt;/p&gt;

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

&lt;p&gt;Once connected you will see your EC2 ubuntu terminal.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What’s Nginx?&lt;/strong&gt;&lt;br&gt;
Nginx is a free, open-source web server known for its high performance and efficient resource usage. It excels in reverse proxy, load balancing, and caching, while also providing HTTPS server capabilities. Designed for maximum performance and stability, Nginx can also serve as a proxy for email protocols like IMAP, POP3, and SMTP, making it a versatile tool for building robust and scalable web applications.&lt;/p&gt;

&lt;p&gt;Firstly  to operate as a root  user,  run the command on your  terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo -i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;update the package list on your instance by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the package is fully updated, install Nginx by running the command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get install nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that is installed, to check the status of nginx web server with this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service nginx status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;To access nginx template html  run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run  view the nginx template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Alternatively, to view the default nginx template, copy the PublicIP address at the bottom of the page and open on a new browser&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Customising your Nginx server&lt;/strong&gt;&lt;br&gt;
At the same file directory to view the current files in the directory,  run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Now to change the template of your nginx server, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano index.nginx-debian.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;using your arrow key clean up the content &lt;/p&gt;

&lt;p&gt;Copy and paste the Html and css file below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;Kalio Princewill&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
    body, html {
    margin: 0;
    padding: 0;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, #6B73FF 0%, #000DFF 100%);
    font-family: 'Montserrat', sans-serif;
}

.container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;
    width: 100%;
}

.glass {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 10px;
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    padding: 20px 40px;
    border: 1px solid rgba(255, 255, 255, 0.3);
    text-align: center;
}

h1, a {
    color: #fff;
    margin: 0;
    font-size: 1em;
}
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class="container"&amp;gt;
        &amp;lt;div class="glass"&amp;gt;
            &amp;lt;h1&amp;gt;Hello world, l just deployed a my status site on AWS EC2&amp;lt;/h1&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you have adding your desire html and css, to save the file use &lt;code&gt;Control + O&lt;/code&gt; , then &lt;code&gt;Enter&lt;/code&gt; finally use &lt;code&gt;Control + X&lt;/code&gt; to exit the the editor.&lt;br&gt;
Refresh your public IP address to see the final result&lt;/p&gt;

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

&lt;p&gt;Your static website is now online and accessible!&lt;/p&gt;

&lt;h3&gt;
  
  
  In summary
&lt;/h3&gt;

&lt;p&gt;This guide provides a step-by-step tutorial on how to deploy a static website using an Nginx web server on an AWS EC2 instance. It begins by introducing AWS EC2, a service for on-demand virtual machines, and outlines the prerequisites such as an AWS account, basic Linux knowledge, and understanding of HTML and CSS. The guide details the process of creating and configuring an EC2 instance, including selecting an Ubuntu image, setting up network security rules, and establishing a key pair for secure access. It then covers the installation and configuration of the Nginx web server on the EC2 instance. The final steps involve customizing the Nginx template to host the static website, resulting in a live and accessible web page.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>nginx</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building a Cryptocurrency News Aggregator with Xata and Cloudinary</title>
      <dc:creator>Kalio Princewill</dc:creator>
      <pubDate>Wed, 23 Nov 2022 16:04:39 +0000</pubDate>
      <link>https://dev.to/hackmamba/building-a-cryptocurrency-news-aggregator-using-xata-and-cloudinary-4818</link>
      <guid>https://dev.to/hackmamba/building-a-cryptocurrency-news-aggregator-using-xata-and-cloudinary-4818</guid>
      <description>&lt;p&gt;Cryptocurrencies are digital assets that are traded on exchanges or pair-to-pair, they vary in value and are susceptible to market news. People who trade these digital assets have to pay attention to news to be informed of market movements because positive news tends to drive up prices while negative news tends to drive down prices.&lt;/p&gt;

&lt;p&gt;Navigating through numerous news sources is challenging for traders as some of these sources publish simultaneously or at various times of the day. This challenge would be well-solved by a news aggregator, which would bridge the information gap by gathering market news and trends on one website that traders could access.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will be discussing how to build a cryptocurrency news aggregator application with React while implementing tools like Cloudinary and Xata.&lt;/p&gt;

&lt;p&gt;At the end of this tutorial, your website should look like the below image.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;I will be using the tools below for this project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React.js&lt;/strong&gt;: React is a front-end JavaScript toolkit that is free and open source for creating user interfaces using UI components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudinary&lt;/strong&gt;: Cloudinary is a platform users deliver, manage, and deliver photos and videos for websites and apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Xata&lt;/strong&gt;: Xata is a serverless data platform  with a spreadsheet-like user interface and an infinitely scalable data API. Xata also functions as a free-text search engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Readers should have the following before building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A text editor&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node&lt;/a&gt; installed on your local computer&lt;/li&gt;
&lt;li&gt;Basic knowledge of Reactjs&lt;/li&gt;
&lt;li&gt;An understanding of how APIs work&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; account&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;In building our cryptocurrency news aggregator, we will create a react app, add features to it, proceed to integrating Cloudinary to manage media upload and Xata for database management.&lt;br&gt;
If you would like to get started with the code right away, you can visit the GitHub repository &lt;a href="https://github.com/kalio007/News-Aggregator" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create react app
&lt;/h2&gt;

&lt;p&gt;On your computer, create a project folder and open it up with your text editor.&lt;/p&gt;

&lt;p&gt;In the project directory, open up a terminal and run the below commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
npx create-react-app news-aggregator

cd news-aggregator

npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;npm start&lt;/code&gt; command is used to start the react application on a development server in your web browser.  Open &lt;code&gt;localhost&lt;/code&gt;&lt;code&gt;:3000&lt;/code&gt; in your browser and you should see the below image&lt;/p&gt;

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

&lt;p&gt;Now your react application is ready, it’s time to setup Cloudinary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudinary Setup
&lt;/h2&gt;

&lt;p&gt;As you already know, Cloudinary is used for media management, and we will be integrating it into the application to manage crypto images on the watch list.&lt;/p&gt;

&lt;p&gt;Before configuring Cloudinary in your React app, install the &lt;a href="https://cloudinary.com/documentation/react_integration" rel="noopener noreferrer"&gt;Cloudinary React.js SDK&lt;/a&gt; in your project using the command below&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i @cloudinary/url-gen @cloudinary/react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;In the root of your project, create a &lt;code&gt;.&lt;/code&gt;&lt;code&gt;env&lt;/code&gt; file on the project folder and include your Cloudinary Cloud name. You should find the cloudName by going to your Cloudinary dashboard.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Register the cloudname as follows in your &lt;code&gt;.env&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;cloudName: 'demo’&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Cloudinary component for global use in your &lt;code&gt;App.js&lt;/code&gt; file by adding the following:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'
    import {AdvancedImage} from '@cloudinary/react';
    import {Cloudinary} from "@cloudinary/url-gen";

    const App = () =&amp;gt; {
      // Create a Cloudinary instance and set your cloud name.
      const cloud = new Cloudinary({
        cloud: {
          cloudName: 'demo'
        }
      })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NB: Replace &lt;code&gt;demo&lt;/code&gt; with your cloudname from cloudinary&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Components
&lt;/h2&gt;

&lt;p&gt;By now you should have an &lt;code&gt;src&lt;/code&gt; folder in your project directory which was created from react.&lt;br&gt;
Inside the folder, create a &lt;code&gt;component folder&lt;/code&gt; which will contain the following files&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;latest.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;navigation.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;news.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;watchlist.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These component files are structures for the application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latest.js&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;latest.js&lt;/code&gt; file and paste the below into it&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

export default function TheLastest(){
    return (
        &amp;lt;h1 className="latest"&amp;gt;
            Latest News...
        &amp;lt;/h1&amp;gt;
      )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Navigation.js&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;navigation.js&lt;/code&gt; file and paste the below into it&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

export default function Nav(){
    return (
        &amp;lt;div&amp;gt;
        &amp;lt;nav&amp;gt;

        &amp;lt;img src="https://cdn4.iconfinder.com/data/icons/summer-line-5/48/sea_lighthouse_ocean_water_beach-256.png"  className="logoimg"/&amp;gt;

        &amp;lt;div&amp;gt;
            &amp;lt;h2 className='logoname'&amp;gt;
            LightCrypto
        &amp;lt;/h2&amp;gt;
        &amp;lt;p className="newstor"&amp;gt;News Aggregator&amp;lt;/p&amp;gt;

        &amp;lt;/div&amp;gt;

        &amp;lt;h2 className='search'&amp;gt;
            &amp;lt;input type= "search" placeholder="search" className = "searchtab" /&amp;gt;
        &amp;lt;/h2&amp;gt;
        &amp;lt;/nav&amp;gt;
    &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;News.js&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;news.js&lt;/code&gt; file and implement the below configuration as well&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from "react";

export default function News(props){
    return (

        &amp;lt;div className="newstab"&amp;gt;
            &amp;lt;div&amp;gt;
            &amp;lt;p className="Arrival"&amp;gt;
                {props.timestamp}
            &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;div className="the-news"&amp;gt;

        &amp;lt;h3 className="the-heading"&amp;gt;
            &amp;lt;a href="{link}" alt="" className="sourceList"&amp;gt;
        {props.heading}
        &amp;lt;/a&amp;gt;
        &amp;lt;/h3&amp;gt;
        &amp;lt;h4 className="the-preview"&amp;gt;
        source: {props.source}
        &amp;lt;/h4&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Options.js&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;options.js&lt;/code&gt; file and paste the below into it&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
export default function Options(){
    return(
        &amp;lt;div className='optionsArea'&amp;gt;
            &amp;lt;div className='optionButton'&amp;gt;
        &amp;lt;h3 className='home'&amp;gt;
            Home
        &amp;lt;/h3&amp;gt;
        &amp;lt;h3 className='calender'&amp;gt;
            &amp;lt;a href= "https://coinmarketcal.com/en/" className='calender' &amp;gt;Calender&amp;lt;/a&amp;gt;
        &amp;lt;/h3&amp;gt;
        &amp;lt;h3 className='aboutUs'&amp;gt;
            About Us
        &amp;lt;/h3&amp;gt;
        &amp;lt;h3&amp;gt;
            &amp;lt;a href= "https://medium.com/@kalio_" className='blog'&amp;gt;Blog&amp;lt;/a&amp;gt;
        &amp;lt;/h3&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className='socails'&amp;gt;
            &amp;lt;h3 className='socailhead'&amp;gt;Socials&amp;lt;/h3&amp;gt;                   
                &amp;lt;div className='twitter'&amp;gt;&amp;lt;img src="https://cdn4.iconfinder.com/data/icons/various-icons-2/476/YouTube.png" alt='youtube' className='twitter-img' /&amp;gt;&amp;lt;a href='https://www.youtube.com/channel/UCGu6O9-vlYHWZT6SFIpp4Kw' className='twitterr'&amp;gt;Youtube&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div className='twitter'&amp;gt;&amp;lt;img src="https://cdn3.iconfinder.com/data/icons/social-network-flat-3/100/Discord-256.png" alt='discord' className='twitter-img' /&amp;gt;&amp;lt;a href='https://twitter.com/Kalio_Prince' className='twitterr'&amp;gt;Discord&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watchlist.js&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a &lt;code&gt;watchlist.js&lt;/code&gt; file and add the following code snippet&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/kalio007/38a25e73127d1cd62b0d8258c432c31a" rel="noopener noreferrer"&gt;https://gist.github.com/kalio007/38a25e73127d1cd62b0d8258c432c31a&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Mock API
&lt;/h2&gt;

&lt;p&gt;Fully fledged news aggregator applications would have API integration to fetch news from numerous news platforms or sources. Since this is not an advanced or complex application at the moment, we would be implementing a mock API which does the job as well. This is how an actual API integration works but the only difference here is we are using a mock version.&lt;br&gt;
The mock API here would be used to fetch news from various sources on its arrival and serve the news to users.&lt;/p&gt;

&lt;p&gt;The API will be arranged in the following manner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;arrival&lt;/li&gt;
&lt;li&gt;link&lt;/li&gt;
&lt;li&gt;heading&lt;/li&gt;
&lt;li&gt;source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Return to the &lt;code&gt;src&lt;/code&gt; folder and create an &lt;code&gt;Api.js&lt;/code&gt; file &lt;code&gt;src/Api.js&lt;/code&gt; and paste the below snippets into it.&lt;br&gt;
&lt;a href="https://gist.github.com/kalio007/96d1db56335143ce7af7c04593e53286" rel="noopener noreferrer"&gt;https://gist.github.com/kalio007/96d1db56335143ce7af7c04593e53286&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to play around with the API by adding your own information&lt;/p&gt;

&lt;p&gt;Open your &lt;code&gt;App.js&lt;/code&gt; file and replace the existing configuration with the  codes snippet below. We are importing the components we developed previously into &lt;code&gt;App.js&lt;/code&gt; and setting up the API to get it working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/kalio007/da8bb215d19dcb4d7cb611616e7fb696" rel="noopener noreferrer"&gt;https://gist.github.com/kalio007/da8bb215d19dcb4d7cb611616e7fb696&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting the CSS styling
&lt;/h2&gt;

&lt;p&gt;I will be using basic CSS to style the application. paste the code snippet below.&lt;br&gt;
&lt;a href="https://gist.github.com/kalio007/9cc3453c3e81cb457c64563742989b1e" rel="noopener noreferrer"&gt;https://gist.github.com/kalio007/9cc3453c3e81cb457c64563742989b1e&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After completing the above steps, restart the application with the &lt;code&gt;npm start&lt;/code&gt; command and your application be ready.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Xata Setup
&lt;/h2&gt;

&lt;p&gt;For this application to manage the database of your application, you need to create an account with Xata by clicking &lt;a href="https://xata.io/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It is completely free to create an account and should take about a minute.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On your &lt;strong&gt;Workspace&lt;/strong&gt;, create a database by clicking &lt;strong&gt;add a database&lt;/strong&gt; which we would be querying from our application.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp57hj4lxflram8girmcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp57hj4lxflram8girmcp.png" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the below on your local terminal&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install typescript @types/node @types/react -D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Next on your terminal install the Xata CLI globally
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g @xata.io/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To login into your Xata, run he Auth login on your terminal
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xata auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create an API key for Xata by clicking &lt;a href="https://xata.io/docs/concepts/api-keys" rel="noopener noreferrer"&gt;here&lt;/a&gt;. To initialize Xata run the following command
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;xata init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Copy and paste the codes below to your main &lt;code&gt;src&lt;/code&gt; file to query the database
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { FC } from 'react';
    import { XataClient } from '../util/xata';

    type Props = Awaited&amp;lt;ReturnType&amp;lt;typeof getServerSideProps&amp;gt;&amp;gt;['props'];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point your application is complete, and the necessary tools have been implemented as I earlier discussed. &lt;/p&gt;

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

&lt;p&gt;This tutorial explains how to build a cryptocurrency news aggregator web application, which I implement Cloudinary to manage media assets and Xata for database management. &lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can visit &lt;a href="https://cloudinary.com/documentation" rel="noopener noreferrer"&gt;Cloudinary’s&lt;/a&gt; official documentation to understand how to get started.&lt;/li&gt;
&lt;li&gt;To get started with &lt;a href="https://xata.io/docs/overview" rel="noopener noreferrer"&gt;Xata&lt;/a&gt; as well, a good place to visit is their official documentation &lt;/li&gt;
&lt;li&gt;Xata has uploaded YouTube videos that walk you through the &lt;a href="https://www.youtube.com/watch?v=-KNRS2fIWdA" rel="noopener noreferrer"&gt;implementation&lt;/a&gt; of the tool in your projects and explain how Xata simplifies &lt;a href="https://www.youtube.com/watch?v=Q_5uJdbubEs" rel="noopener noreferrer"&gt;development&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudinary</category>
      <category>xata</category>
      <category>serverless</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
