<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://benlabs.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://benlabs.dev/" rel="alternate" type="text/html" /><updated>2026-07-31T13:08:23+00:00</updated><id>https://benlabs.dev/feed.xml</id><title type="html">Luis Benavides</title><subtitle>Lead Cloud &amp; DevOps Engineer specializing in AWS, Infrastructure as Code, Kubernetes, and CI/CD — plus writing on cloud infrastructure.</subtitle><author><name>Luis Benavides</name></author><entry><title type="html">Lessons Learned Building an EMR Serverless Development Environment</title><link href="https://benlabs.dev/blog/2026/02/28/lessons-learned-emr-serverless/" rel="alternate" type="text/html" title="Lessons Learned Building an EMR Serverless Development Environment" /><published>2026-02-28T00:00:00+00:00</published><updated>2026-02-28T00:00:00+00:00</updated><id>https://benlabs.dev/blog/2026/02/28/lessons-learned-emr-serverless</id><content type="html" xml:base="https://benlabs.dev/blog/2026/02/28/lessons-learned-emr-serverless/"><![CDATA[<p>After setting up an EMR Serverless development environment with Terraform and actually using it for testing workloads, a few practical lessons became clear. While the service is marketed as “serverless,” working with it effectively still requires thoughtful infrastructure design, especially around networking, permissions, and observability.</p>

<p>Here are some of the key takeaways from that experience.</p>

<hr />

<h2 id="serverless-does-not-mean-networkless">Serverless Does Not Mean Networkless</h2>

<p>One of the biggest surprises is how much networking still matters. EMR Serverless jobs typically run inside your VPC when accessing private resources, which means subnet configuration, routing, DNS, and endpoints must be correct. Misconfigured networking often leads to vague runtime failures that are harder to debug than simple infrastructure errors. Treat networking as a first-class design concern even when using serverless compute.</p>

<hr />

<h2 id="add-the-s3-vpc-endpoint-early">Add the S3 VPC Endpoint Early</h2>

<p>If your workloads interact heavily with S3 — which most EMR jobs do — a gateway VPC endpoint for S3 is almost always worth adding. Without it, traffic flows through the NAT gateway, increasing both latency and cost. This is especially noticeable in development environments where iterative testing can generate significant traffic. The endpoint simplifies connectivity and usually reduces operational friction.</p>

<hr />

<h2 id="iam-roles-are-usually-the-main-friction-point">IAM Roles Are Usually the Main Friction Point</h2>

<p>Permissions tend to be the most time-consuming part of an EMR Serverless setup. The execution role needs access to multiple components simultaneously: EMR-managed artifacts, your own S3 buckets, CloudWatch logs, and networking resources like ENIs. Missing a single permission often results in runtime job failures rather than clear infrastructure errors. Starting with a functional policy and tightening it iteratively often works better than over-optimizing upfront.</p>

<hr />

<h2 id="emr-studio-significantly-improves-developer-experience">EMR Studio Significantly Improves Developer Experience</h2>

<p>While not strictly required, EMR Studio makes experimentation noticeably easier. Interactive notebooks, centralized access to logs, and a browser-based interface reduce the barrier for developers who are less familiar with EMR CLI workflows. For development environments focused on testing, prototyping, or onboarding new users, Studio is usually worth including.</p>

<hr />

<h2 id="capacity-configuration-requires-some-thought">Capacity Configuration Requires Some Thought</h2>

<p>Initial and maximum capacity settings for EMR Serverless directly impact both cost and performance. Conservative defaults combined with auto-start and auto-stop settings tend to work well for development environments. Too little capacity leads to throttled jobs, while overly generous limits can result in unnecessary cost spikes during testing.</p>

<hr />

<h2 id="observability-from-day-one-saves-time-later">Observability From Day One Saves Time Later</h2>

<p>Distributed data workloads rarely fail in obvious ways. Centralizing logs in CloudWatch from the beginning makes troubleshooting significantly easier. Retrofitting logging after problems appear is far more painful than enabling it upfront, even in non-production environments.</p>

<hr />

<h2 id="final-reflection">Final Reflection</h2>

<p>EMR Serverless simplifies infrastructure management, but it doesn’t eliminate architectural thinking. Networking, IAM design, cost awareness, and observability still matter. Treating it as “just another serverless service” often leads to unnecessary friction, while approaching it as a distributed data platform yields much better results.</p>

<p>These lessons informed the Terraform environment presented earlier in this series and continue to shape how I approach EMR-based workloads moving forward.</p>]]></content><author><name>Luis Benavides</name></author><category term="aws" /><category term="devops" /><category term="lessons-learned" /><category term="aws" /><category term="emr" /><category term="serverless" /><category term="data" /><category term="terraform" /><category term="devops" /><summary type="html"><![CDATA[After setting up an EMR Serverless development environment with Terraform and actually using it for testing workloads, a few practical lessons became clear. While the service is marketed as “serverless,” working with it effectively still requires thoughtful infrastructure design, especially around networking, permissions, and observability.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://benlabs.dev/assets/images/og-image.png" /><media:content medium="image" url="https://benlabs.dev/assets/images/og-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Testing EMR Serverless Like a Developer: A Practical Walkthrough of the Test Suite</title><link href="https://benlabs.dev/blog/2026/02/27/testing-emr-serverless/" rel="alternate" type="text/html" title="Testing EMR Serverless Like a Developer: A Practical Walkthrough of the Test Suite" /><published>2026-02-27T00:00:00+00:00</published><updated>2026-02-27T00:00:00+00:00</updated><id>https://benlabs.dev/blog/2026/02/27/testing-emr-serverless</id><content type="html" xml:base="https://benlabs.dev/blog/2026/02/27/testing-emr-serverless/"><![CDATA[<p>Once your Terraform stack is up, the test layer is what proves the environment is actually usable for day-to-day development. In this project, tests are intentionally script-driven: simple, repeatable, and close to how engineers really submit Spark jobs.</p>

<h2 id="what-the-tests-are-validating"><strong>What the tests are validating</strong></h2>

<p>The test suite is validating three essential capabilities of your EMR Serverless setup:</p>

<ul>
  <li>You can submit jobs to the EMR Serverless application successfully.</li>
  <li>The execution role has the right permissions for S3 and CloudWatch.</li>
  <li>The single project S3 bucket works as the artifact/input/output location.</li>
  <li>Logs are emitted to CloudWatch and can be inspected for debugging.</li>
</ul>

<p>This is less about unit testing code and more about end-to-end platform verification.</p>

<h2 id="test-structure"><strong>Test structure</strong></h2>

<p>Under terraform/projects/data/emr-serverless/tests/ you have three test scenarios:</p>

<ul>
  <li>test_pi/: a minimal Spark Pi validation.</li>
  <li>test_food/: a data-processing job over a sample CSV dataset.</li>
  <li>benchmark/: a heavier benchmark-style run using the Spark benchmark JAR.</li>
</ul>

<p>Each scenario includes:</p>

<ul>
  <li>a markdown guide (.md) describing steps,</li>
  <li>a runnable shell script (submit_job*.sh) that submits the job,</li>
  <li>and, where needed, sample data or job code.</li>
</ul>

<h2 id="shared-test-pattern"><strong>Shared test pattern</strong></h2>

<p>All tests follow the same runtime contract:</p>

<ol>
  <li>Read Terraform outputs from the deployed stack:
    <ul>
      <li>application_id</li>
      <li>execution_role_arn</li>
      <li>bucket_name</li>
      <li>log_group_name</li>
      <li>region</li>
    </ul>
  </li>
  <li>Export those values as environment variables.</li>
  <li>Run a submission script (submit_job_pi.sh, submit_job_health.sh, submit_job.sh).</li>
  <li>Inspect job run status with aws emr-serverless get-job-run.</li>
</ol>

<p>This pattern is excellent for a blog because readers can copy/paste and adapt quickly.</p>

<h2 id="test-1-spark-pi-test_pi"><strong>Test 1: Spark Pi (test_pi)</strong></h2>

<p>test_pi is your smoke test. It answers one question: “Can I run anything at all?”</p>

<p>Why it matters:</p>

<ul>
  <li>fastest feedback loop,</li>
  <li>verifies app availability, role trust, and basic Spark runtime path,</li>
  <li>ideal as the first check after terraform apply.</li>
</ul>

<p>If this fails, there is no point running bigger jobs yet.</p>

<h2 id="test-2-food-dataset-job-test_food"><strong>Test 2: Food dataset job (test_food)</strong></h2>

<p>This is closer to a real dev workflow: submit a custom Python/Spark job with input data and process results.</p>

<p>Why it matters:</p>

<ul>
  <li>validates project bucket read/write behavior,</li>
  <li>validates job packaging and submission arguments,</li>
  <li>validates end-to-end data path (input -&gt; processing -&gt; output/logs).</li>
</ul>

<p>For most teams, this is the first “real confidence” test.</p>

<h2 id="test-3-benchmark-run-benchmark"><strong>Test 3: Benchmark run (benchmark)</strong></h2>

<p>This scenario runs a larger benchmark-style workload.</p>

<p>Why it matters:</p>

<ul>
  <li>validates capacity and scaling behavior under heavier load,</li>
  <li>helps tune initial_capacity, maximum_capacity, and idle timeout,</li>
  <li>surfaces bottlenecks in startup time, artifact loading, and logging behavior.</li>
</ul>

<p>This is where platform tuning starts, not just platform validation.</p>

<h2 id="why-this-test-design-works-well"><strong>Why this test design works well</strong></h2>

<p>This test setup is strong for a developer platform because it is:</p>

<ul>
  <li>Deterministic: all scripts consume Terraform outputs, not hardcoded IDs.</li>
  <li>Portable: same scripts work across environments if outputs are exported.</li>
  <li>Operational: uses the exact AWS CLI path teams use in CI/CD and troubleshooting.</li>
  <li>Incremental: smoke test -&gt; functional test -&gt; performance-oriented test.</li>
</ul>

<h2 id="suggested-blog-framing"><strong>Suggested blog framing</strong></h2>

<p>If you publish this as a blog section, position it as:</p>

<ul>
  <li>“Deploy infrastructure with Terraform.”</li>
  <li>“Validate with three progressive test levels.”</li>
  <li>“Use outputs + scripts as the stable contract between infra and workloads.”</li>
</ul>

<p>That message makes the project feel production-minded, even in a dev/test environment.</p>]]></content><author><name>Luis Benavides</name></author><category term="aws" /><category term="testing" /><category term="data-engineering" /><category term="aws" /><category term="emr" /><category term="serverless" /><category term="data" /><category term="testing" /><summary type="html"><![CDATA[Once your Terraform stack is up, the test layer is what proves the environment is actually usable for day-to-day development. In this project, tests are intentionally script-driven: simple, repeatable, and close to how engineers really submit Spark jobs.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://benlabs.dev/assets/images/og-image.png" /><media:content medium="image" url="https://benlabs.dev/assets/images/og-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">From Zero to EMR Serverless Dev Environment with Terraform</title><link href="https://benlabs.dev/blog/2026/02/15/emr-serverless-terraform/" rel="alternate" type="text/html" title="From Zero to EMR Serverless Dev Environment with Terraform" /><published>2026-02-15T00:00:00+00:00</published><updated>2026-02-15T00:00:00+00:00</updated><id>https://benlabs.dev/blog/2026/02/15/emr-serverless-terraform</id><content type="html" xml:base="https://benlabs.dev/blog/2026/02/15/emr-serverless-terraform/"><![CDATA[<p>This Terraform setup provisions a practical EMR Serverless development environment on AWS — not just isolated resources, but a coherent workspace where engineers can actually experiment, iterate, and understand how EMR behaves in real conditions. It includes networking, storage, IAM roles, logging, and EMR Studio so developers can run Spark workloads without spending days wiring infrastructure manually.</p>

<p>The goal here is not “deploy infrastructure for the sake of it.” The goal is to remove friction from testing distributed data workloads.</p>

<hr />

<h2 id="architecture-overview">Architecture overview</h2>

<p>The diagram below summarizes how the main pieces fit together: inputs (variables) drive a VPC with public and private subnets, EMR Serverless and EMR Studio in the private tier, an S3 gateway endpoint for in-VPC access to the bucket, and regional resources (S3, CloudWatch) plus IAM roles wired to the outputs you use from scripts and CI.</p>

<figure class="diagram">
  <svg class="arch-diagram" viewBox="0 0 980 720" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="archTitle archDesc">
  <title id="archTitle">EMR Serverless development environment architecture</title>
  <desc id="archDesc">
    Terraform inputs configure three groups: IAM roles, a VPC module, and regional
    services. The VPC holds public subnets with a NAT gateway and internet gateway,
    and private subnets where a security group contains the EMR Serverless
    application and EMR Studio. Those reach the S3 bucket through an S3 gateway
    endpoint, and send logs to a CloudWatch log group. All three groups surface
    Terraform outputs.
  </desc>

  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
      <path class="arrowhead" d="M0,1 L9,5 L0,9 z" />
    </marker>
  </defs>

  <g class="edges" fill="none" stroke-width="1.5" marker-end="url(#arrow)">
    <!-- inputs feed each group -->
    <path d="M109,112 V206" />
    <path d="M465,112 V146" />
    <path d="M846,112 V206" />

    <!-- IAM roles attach to compute -->
    <path d="M176,265 H216 V363 H282" />
    <path d="M176,315 H202 V417 H282" />

    <!-- private compute reaches S3 through the gateway endpoint -->
    <path d="M389,458 V490" />
    <path d="M496,507 H726 V338 H750" />

    <!-- logs -->
    <path d="M496,363 H712 V268 H750" />

    <!-- egress -->
    <path d="M442,231 H474" />

    <!-- each group surfaces outputs -->
    <path d="M109,360 V592" />
    <path d="M465,560 V592" />
    <path d="M846,400 V592" />
  </g>

  <!-- ============================ inputs ============================ -->
  <g class="group group--io">
    <rect x="24" y="28" width="932" height="84" rx="10" />
    <text class="group-label" x="42" y="50">inputs</text>
  </g>
  <g class="node node--input">
    <rect x="46" y="62" width="168" height="34" rx="6" />
    <text x="130" y="84">region</text>
    <rect x="226" y="62" width="168" height="34" rx="6" />
    <text x="310" y="84">project_name</text>
    <rect x="406" y="62" width="168" height="34" rx="6" />
    <text x="490" y="84">emr_release_label</text>
    <rect x="586" y="62" width="168" height="34" rx="6" />
    <text x="670" y="84">emr_serverless</text>
    <rect x="766" y="62" width="168" height="34" rx="6" />
    <text x="850" y="84">tags</text>
  </g>

  <!-- ============================== IAM ============================= -->
  <g class="group">
    <rect x="24" y="210" width="170" height="150" rx="10" />
    <text class="group-label" x="42" y="232">IAM</text>
  </g>
  <g class="node node--iam">
    <rect x="42" y="248" width="134" height="34" rx="6" />
    <text x="109" y="270">Execution Role</text>
    <rect x="42" y="298" width="134" height="34" rx="6" />
    <text x="109" y="320">Studio Role</text>
  </g>

  <!-- ============================== VPC ============================= -->
  <g class="group group--vpc">
    <rect x="230" y="150" width="470" height="410" rx="12" />
    <text class="group-label" x="248" y="172">VPC module</text>
  </g>

  <g class="subgroup">
    <rect x="250" y="186" width="430" height="76" rx="8" />
    <text class="subgroup-label" x="266" y="204">Public subnets · 2 AZs</text>
  </g>
  <g class="node">
    <rect x="272" y="214" width="170" height="34" rx="6" />
    <text x="357" y="236">NAT Gateway</text>
    <rect x="478" y="214" width="170" height="34" rx="6" />
    <text x="563" y="236">Internet Gateway</text>
  </g>

  <g class="subgroup">
    <rect x="250" y="282" width="430" height="194" rx="8" />
    <text class="subgroup-label" x="266" y="300">Private subnets · 2 AZs</text>
  </g>

  <g class="subgroup subgroup--sg">
    <rect x="266" y="312" width="250" height="146" rx="8" />
    <text class="subgroup-label" x="282" y="330">Security Group</text>
  </g>
  <g class="node node--compute">
    <rect x="282" y="344" width="214" height="38" rx="6" />
    <text x="389" y="368">EMR Serverless App</text>
    <rect x="282" y="398" width="214" height="38" rx="6" />
    <text x="389" y="422">EMR Studio</text>
  </g>

  <g class="node node--net">
    <rect x="282" y="490" width="214" height="34" rx="6" />
    <text x="389" y="512">S3 Gateway Endpoint</text>
  </g>

  <!-- ============================ regional =========================== -->
  <g class="group">
    <rect x="736" y="210" width="220" height="190" rx="10" />
    <text class="group-label" x="754" y="232">Regional</text>
  </g>
  <g class="node node--data">
    <rect x="754" y="250" width="184" height="36" rx="6" />
    <text x="846" y="273">CloudWatch Logs</text>
    <rect x="754" y="320" width="184" height="36" rx="6" />
    <text x="846" y="343">S3 Bucket</text>
  </g>

  <!-- ============================ outputs =========================== -->
  <g class="group group--io">
    <rect x="24" y="596" width="932" height="96" rx="10" />
    <text class="group-label" x="42" y="618">outputs</text>
  </g>
  <g class="node node--output">
    <rect x="39" y="634" width="142" height="34" rx="6" />
    <text x="110" y="656">application_id</text>
    <rect x="191" y="634" width="142" height="34" rx="6" />
    <text x="262" y="656">execution_role_arn</text>
    <rect x="343" y="634" width="142" height="34" rx="6" />
    <text x="414" y="656">emr_studio_url</text>
    <rect x="495" y="634" width="142" height="34" rx="6" />
    <text x="566" y="656">bucket_name</text>
    <rect x="647" y="634" width="142" height="34" rx="6" />
    <text x="718" y="656">log_group_name</text>
    <rect x="799" y="634" width="142" height="34" rx="6" />
    <text x="870" y="656">region</text>
  </g>
</svg>

  <figcaption>
    Terraform inputs configure the IAM roles, the VPC module, and the regional
    services; each group surfaces the outputs used from scripts and CI.
  </figcaption>
</figure>

<hr />

<h2 id="why-this-stack-exists">Why this stack exists</h2>

<p>If you’ve ever tried to test EMR seriously, you probably noticed something: it’s not trivial to spin up a safe, reproducible dev environment. Permissions, networking, logging, data access, runtime dependencies — everything needs to line up, and when one piece is missing, debugging quickly becomes painful.</p>

<p>This stack gives developers a controlled space to:</p>

<ul>
  <li>run Spark jobs on EMR Serverless without touching production,</li>
  <li>keep scripts, dependencies, inputs, and outputs in a single S3 location,</li>
  <li>inspect logs centrally in CloudWatch,</li>
  <li>interact through EMR Studio instead of raw CLI workflows.</li>
</ul>

<p>Everything is split into logical Terraform files so the environment remains understandable and maintainable rather than becoming a monolithic IaC blob.</p>

<hr />

<h2 id="terraform-resources-summary">Terraform resources summary</h2>

<p>This table summarizes the Terraform resources that make up the EMR Serverless development environment: networking, compute, security, IAM, storage, and observability.</p>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>Resource / module</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Networking</td>
      <td>module.vpc</td>
      <td>VPC, 2 public + 2 private subnets, 1 NAT, IGW</td>
    </tr>
    <tr>
      <td>Networking</td>
      <td>aws_vpc_endpoint.s3</td>
      <td>S3 Gateway endpoint on private route tables</td>
    </tr>
    <tr>
      <td>Compute</td>
      <td>aws_emrserverless_application.main</td>
      <td>Spark app (driver + executors in private subnets)</td>
    </tr>
    <tr>
      <td>Compute</td>
      <td>aws_emr_studio.main</td>
      <td>EMR Studio (notebooks, in private subnets)</td>
    </tr>
    <tr>
      <td>Security</td>
      <td>aws_security_group.emr_serverless</td>
      <td>Single SG for app + studio (egress only)</td>
    </tr>
    <tr>
      <td>IAM</td>
      <td>aws_iam_role.emr_serverless_execution</td>
      <td>Role + inline policy (S3, CW, EC2 ENI in VPC)</td>
    </tr>
    <tr>
      <td>IAM</td>
      <td>aws_iam_role.emr_studio</td>
      <td>Role + Editors, S3Full, EMRFull</td>
    </tr>
    <tr>
      <td>Storage</td>
      <td>aws_s3_bucket.emr</td>
      <td>One bucket: logs, libraries, studio, tests</td>
    </tr>
    <tr>
      <td>Observability</td>
      <td>aws_cloudwatch_log_group.emr</td>
      <td>Log group for EMR Serverless</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="datatf-environment-awareness-and-policy-composition">data.tf: environment awareness and policy composition</h2>

<p>This file doesn’t create infrastructure. It teaches Terraform about the environment it’s operating in and builds IAM policy documents dynamically.</p>

<h3 id="context-lookups">Context lookups</h3>

<p>These data sources remove hardcoded assumptions:</p>

<ul>
  <li>availability zones for resilient subnet placement,</li>
  <li>account ID for deterministic naming,</li>
  <li>region and partition so ARNs work across standard AWS, GovCloud, or other partitions.</li>
</ul>

<p>That might sound minor, but this is what makes infrastructure portable instead of brittle.</p>

<h3 id="iam-policy-documents">IAM policy documents</h3>

<p>This is where most EMR deployments either become secure… or messy.</p>

<p>We generate:</p>

<ul>
  <li>a trust policy for EMR Studio,</li>
  <li>a trust policy for EMR Serverless execution,</li>
  <li>an execution policy that allows:
    <ul>
      <li>access to AWS-managed EMR runtime artifacts,</li>
      <li>controlled access to your project S3 bucket,</li>
      <li>network interface creation inside the VPC,</li>
      <li>log publishing to CloudWatch.</li>
    </ul>
  </li>
</ul>

<p>Using <code class="language-plaintext highlighter-rouge">aws_iam_policy_document</code> keeps policies readable, composable, and version-controlled. It produces JSON only; actual IAM roles attach it later.</p>

<hr />

<h2 id="maintf-building-the-actual-playground">main.tf: building the actual playground</h2>

<p>This file turns design into running infrastructure.</p>

<h3 id="networking--isolation-with-connectivity">Networking — isolation with connectivity</h3>

<p>The VPC module creates:</p>

<ul>
  <li>private subnets for compute isolation,</li>
  <li>public subnets mainly for NAT,</li>
  <li>DNS support so AWS service discovery works,</li>
  <li>a NAT gateway so workloads can reach required AWS endpoints safely.</li>
</ul>

<p>An S3 VPC endpoint is added intentionally. Without it, Spark jobs in private subnets would route S3 traffic through NAT, increasing cost and latency. This small addition usually pays for itself quickly.</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="s2">"vpc"</span> <span class="p">{</span>
  <span class="nx">source</span>  <span class="o">=</span> <span class="s2">"terraform-aws-modules/vpc/aws"</span>
  <span class="nx">version</span> <span class="o">=</span> <span class="s2">"5.8.1"</span>

  <span class="nx">name</span>                 <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-vpc"</span>
  <span class="nx">cidr</span>                 <span class="o">=</span> <span class="s2">"10.100.0.0/16"</span>
  <span class="nx">azs</span>                  <span class="o">=</span> <span class="nx">slice</span><span class="err">(</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_availability_zones</span><span class="p">.</span><span class="nx">available</span><span class="p">.</span><span class="nx">names</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
  <span class="nx">private_subnets</span>      <span class="o">=</span> <span class="p">[</span><span class="s2">"10.100.1.0/24"</span><span class="p">,</span> <span class="s2">"10.100.2.0/24"</span><span class="p">]</span>
  <span class="nx">public_subnets</span>       <span class="o">=</span> <span class="p">[</span><span class="s2">"10.100.101.0/24"</span><span class="p">,</span> <span class="s2">"10.100.102.0/24"</span><span class="p">]</span>
  <span class="nx">enable_nat_gateway</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">single_nat_gateway</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">enable_dns_hostnames</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">enable_dns_support</span>   <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">tags</span>                 <span class="o">=</span> <span class="nx">merge</span><span class="err">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-vpc"</span> <span class="p">})</span>
<span class="p">}</span>

<span class="k">resource</span> <span class="s2">"aws_vpc_endpoint"</span> <span class="s2">"s3"</span> <span class="p">{</span>
  <span class="nx">vpc_id</span>            <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="nx">vpc</span><span class="p">.</span><span class="nx">vpc_id</span>
  <span class="nx">service_name</span>      <span class="o">=</span> <span class="s2">"com.amazonaws.</span><span class="p">${</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_region</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">region</span><span class="p">}</span><span class="s2">.s3"</span>
  <span class="nx">vpc_endpoint_type</span> <span class="o">=</span> <span class="s2">"Gateway"</span>
  <span class="nx">route_table_ids</span>   <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="nx">vpc</span><span class="p">.</span><span class="nx">private_route_table_ids</span>
  <span class="nx">tags</span>              <span class="o">=</span> <span class="nx">merge</span><span class="p">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-s3-ep"</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="storage--the-operational-anchor">Storage — the operational anchor</h3>

<p>The S3 bucket becomes the central artifact store:</p>

<ul>
  <li>job scripts,</li>
  <li>runtime dependencies,</li>
  <li>intermediate outputs,</li>
  <li>logs and test artifacts.</li>
</ul>

<p>Public access is blocked, versioning protects against accidental overwrites, and encryption is enforced by default. These aren’t luxury settings; they’re baseline operational hygiene.</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_s3_bucket"</span> <span class="s2">"emr"</span> <span class="p">{</span>
  <span class="nx">bucket</span>        <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-bucket-</span><span class="p">${</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_caller_identity</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">account_id</span><span class="p">}</span><span class="s2">"</span>
  <span class="nx">force_destroy</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">tags</span>          <span class="o">=</span> <span class="nx">merge</span><span class="p">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-bucket"</span> <span class="p">})</span>
<span class="p">}</span>

<span class="k">resource</span> <span class="s2">"aws_s3_bucket_public_access_block"</span> <span class="s2">"emr"</span> <span class="p">{</span>
  <span class="nx">bucket</span> <span class="o">=</span> <span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">emr</span><span class="p">.</span><span class="nx">id</span>

  <span class="nx">block_public_acls</span>       <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">block_public_policy</span>     <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">ignore_public_acls</span>      <span class="o">=</span> <span class="kc">true</span>
  <span class="nx">restrict_public_buckets</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="logging--observability-from-day-one">Logging — observability from day one</h3>

<p>A dedicated CloudWatch log group captures driver and executor logs. When something fails in distributed processing, logs are often the only way to understand what happened. Centralizing logs from day one avoids painful retrofits later.</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_cloudwatch_log_group"</span> <span class="s2">"emr"</span> <span class="p">{</span>
  <span class="nx">name</span>              <span class="o">=</span> <span class="s2">"/aws/emr-serverless/</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">"</span>
  <span class="nx">retention_in_days</span> <span class="o">=</span> <span class="mi">7</span>
  <span class="nx">tags</span>              <span class="o">=</span> <span class="nx">merge</span><span class="p">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-log-group"</span> <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="security-and-iam--where-most-complexity-lives">Security and IAM — where most complexity lives</h3>

<p>Two roles matter here:</p>

<ul>
  <li><strong>EMR Serverless execution role</strong> — what Spark jobs actually run as.</li>
  <li><strong>EMR Studio service role</strong> — what enables the interactive workspace.</li>
</ul>

<p>Security groups remain intentionally simple: outbound allowed, inbound restricted. EMR Serverless relies heavily on AWS-managed service communication, so overly strict network rules often cause subtle failures that are difficult to diagnose.</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"emr_serverless_execution"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-execution-role"</span>
  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="nx">aws_iam_policy_document</span><span class="p">.</span><span class="nx">emr_serverless_assume</span><span class="p">.</span><span class="nx">json</span>
  <span class="nx">tags</span>               <span class="o">=</span> <span class="nx">merge</span><span class="p">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-execution-role"</span> <span class="p">})</span>
<span class="p">}</span>
<span class="k">resource</span> <span class="s2">"aws_iam_role_policy"</span> <span class="s2">"emr_serverless_execution"</span> <span class="p">{</span>
  <span class="nx">name</span>   <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-execution-policy"</span>
  <span class="nx">role</span>   <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">emr_serverless_execution</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">policy</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="nx">aws_iam_policy_document</span><span class="p">.</span><span class="nx">emr_serverless_execution</span><span class="p">.</span><span class="nx">json</span>
<span class="p">}</span>

<span class="k">resource</span> <span class="s2">"aws_iam_role"</span> <span class="s2">"emr_studio"</span> <span class="p">{</span>
  <span class="nx">name</span>               <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-studio-role"</span>
  <span class="nx">assume_role_policy</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="nx">aws_iam_policy_document</span><span class="p">.</span><span class="nx">emr_studio_assume</span><span class="p">.</span><span class="nx">json</span>
  <span class="nx">tags</span>               <span class="o">=</span> <span class="nx">merge</span><span class="p">(</span><span class="kd">var</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Name</span> <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-studio-role"</span> <span class="p">})</span>
<span class="p">}</span>
<span class="k">resource</span> <span class="s2">"aws_iam_role_policy_attachment"</span> <span class="s2">"emr_studio_editors"</span> <span class="p">{</span>
  <span class="nx">role</span>       <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">emr_studio</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">policy_arn</span> <span class="o">=</span> <span class="s2">"arn:</span><span class="p">${</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_partition</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">partition</span><span class="p">}</span><span class="s2">:iam::aws:policy/service-role/AmazonElasticMapReduceEditorsRole"</span>
<span class="p">}</span>
<span class="k">resource</span> <span class="s2">"aws_iam_role_policy_attachment"</span> <span class="s2">"emr_studio_s3"</span> <span class="p">{</span>
  <span class="nx">role</span>       <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">emr_studio</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">policy_arn</span> <span class="o">=</span> <span class="s2">"arn:</span><span class="p">${</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_partition</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">partition</span><span class="p">}</span><span class="s2">:iam::aws:policy/AmazonS3FullAccess"</span>
<span class="p">}</span>
<span class="k">resource</span> <span class="s2">"aws_iam_role_policy_attachment"</span> <span class="s2">"emr_studio_emr"</span> <span class="p">{</span>
  <span class="nx">role</span>       <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">emr_studio</span><span class="p">.</span><span class="nx">name</span>
  <span class="nx">policy_arn</span> <span class="o">=</span> <span class="s2">"arn:</span><span class="p">${</span><span class="k">data</span><span class="p">.</span><span class="nx">aws_partition</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">partition</span><span class="p">}</span><span class="s2">:iam::aws:policy/AmazonElasticMapReduceFullAccess"</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="compute-and-developer-interface">Compute and developer interface</h3>

<p>This is where the environment becomes usable:</p>

<ul>
  <li>EMR Serverless application configured for Spark workloads, auto-start/stop behavior, and defined capacity boundaries.</li>
  <li>EMR Studio connected to the VPC, security group, service role, and S3 location so developers can run notebooks, inspect jobs, and iterate quickly.</li>
</ul>

<p>Without Studio, EMR experimentation tends to become CLI-heavy and slower for most teams.</p>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_emrserverless_application"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">name</span>          <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-app"</span>
  <span class="nx">release_label</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_release_label</span>
  <span class="nx">type</span>          <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">application_type</span>
  <span class="nx">architecture</span>  <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">architecture</span>

  <span class="nx">auto_start_configuration</span> <span class="p">{</span>
    <span class="nx">enabled</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span>
  <span class="nx">auto_stop_configuration</span> <span class="p">{</span>
    <span class="nx">enabled</span>              <span class="o">=</span> <span class="kc">true</span>
    <span class="nx">idle_timeout_minutes</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">auto_stop_idle_minutes</span>
  <span class="p">}</span>

  <span class="nx">network_configuration</span> <span class="p">{</span>
    <span class="nx">subnet_ids</span>         <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="nx">vpc</span><span class="p">.</span><span class="nx">private_subnets</span>
    <span class="nx">security_group_ids</span> <span class="o">=</span> <span class="p">[</span><span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">id</span><span class="p">]</span>
  <span class="p">}</span>

  <span class="nx">initial_capacity</span> <span class="p">{</span>
    <span class="nx">initial_capacity_type</span> <span class="o">=</span> <span class="s2">"Driver"</span>
    <span class="nx">initial_capacity_config</span> <span class="p">{</span>
      <span class="nx">worker_count</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">driver</span><span class="p">.</span><span class="nx">worker_count</span>
      <span class="nx">worker_configuration</span> <span class="p">{</span>
        <span class="nx">cpu</span>    <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">driver</span><span class="p">.</span><span class="nx">cpu</span>
        <span class="nx">memory</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">driver</span><span class="p">.</span><span class="nx">memory</span>
        <span class="nx">disk</span>   <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">driver</span><span class="p">.</span><span class="nx">disk</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="nx">initial_capacity</span> <span class="p">{</span>
    <span class="nx">initial_capacity_type</span> <span class="o">=</span> <span class="s2">"Executor"</span>
    <span class="nx">initial_capacity_config</span> <span class="p">{</span>
      <span class="nx">worker_count</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">executor</span><span class="p">.</span><span class="nx">worker_count</span>
      <span class="nx">worker_configuration</span> <span class="p">{</span>
        <span class="nx">cpu</span>    <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">executor</span><span class="p">.</span><span class="nx">cpu</span>
        <span class="nx">memory</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">executor</span><span class="p">.</span><span class="nx">memory</span>
        <span class="nx">disk</span>   <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">executor</span><span class="p">.</span><span class="nx">disk</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="nx">maximum_capacity</span> <span class="p">{</span>
    <span class="nx">cpu</span>    <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">maximum_capacity</span><span class="p">.</span><span class="nx">cpu</span>
    <span class="nx">memory</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">maximum_capacity</span><span class="p">.</span><span class="nx">memory</span>
    <span class="nx">disk</span>   <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">maximum_capacity</span><span class="p">.</span><span class="nx">disk</span>
  <span class="p">}</span>

  <span class="nx">tags</span> <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">tags</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-terraform highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">resource</span> <span class="s2">"aws_emr_studio"</span> <span class="s2">"main"</span> <span class="p">{</span>
  <span class="nx">name</span>                        <span class="o">=</span> <span class="s2">"</span><span class="p">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">project_name</span><span class="p">}</span><span class="s2">-studio"</span>
  <span class="nx">auth_mode</span>                   <span class="o">=</span> <span class="s2">"IAM"</span>
  <span class="nx">vpc_id</span>                      <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="nx">vpc</span><span class="p">.</span><span class="nx">vpc_id</span>
  <span class="nx">subnet_ids</span>                  <span class="o">=</span> <span class="k">module</span><span class="p">.</span><span class="nx">vpc</span><span class="p">.</span><span class="nx">private_subnets</span>
  <span class="nx">service_role</span>                <span class="o">=</span> <span class="nx">aws_iam_role</span><span class="p">.</span><span class="nx">emr_studio</span><span class="p">.</span><span class="nx">arn</span>
  <span class="nx">workspace_security_group_id</span> <span class="o">=</span> <span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">engine_security_group_id</span>    <span class="o">=</span> <span class="nx">aws_security_group</span><span class="p">.</span><span class="nx">emr_serverless</span><span class="p">.</span><span class="nx">id</span>
  <span class="nx">default_s3_location</span>         <span class="o">=</span> <span class="s2">"s3://</span><span class="p">${</span><span class="nx">aws_s3_bucket</span><span class="p">.</span><span class="nx">emr</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">/studio/"</span>
  <span class="nx">tags</span>                        <span class="o">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">tags</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="variablestf-making-the-stack-reusable">variables.tf: making the stack reusable</h2>

<p>This file abstracts environment-specific settings:</p>

<ul>
  <li>region and naming,</li>
  <li>EMR release label,</li>
  <li>capacity tuning (driver/executor sizing, idle timeout),</li>
  <li>tagging standards.</li>
</ul>

<p>Separating configuration from infrastructure code keeps the module reusable across dev, staging, or experimental environments without rewriting resources.</p>

<hr />

<h2 id="outputstf-operational-handoff">outputs.tf: operational handoff</h2>

<p>Outputs expose what engineers actually need after deployment:</p>

<ul>
  <li>EMR Serverless application ID,</li>
  <li>execution role ARN,</li>
  <li>Studio URL,</li>
  <li>S3 bucket name,</li>
  <li>log group,</li>
  <li>region.</li>
</ul>

<p>These values typically feed CI pipelines, test scripts, or job submission tooling.</p>

<hr />

<h2 id="terraformtfvarsexample-deployment-profiles">terraform.tfvars(.example): deployment profiles</h2>

<p>These files define concrete environment values. Think of them as profiles:</p>

<ul>
  <li>which region,</li>
  <li>how large the EMR application should be,</li>
  <li>naming conventions,</li>
  <li>tagging policies.</li>
</ul>

<p>This separation makes spinning up multiple environments predictable.</p>

<hr />

<p>This environment isn’t intended to be production-ready out of the box. It’s designed to make experimentation with EMR Serverless practical: reproducible infrastructure, sensible defaults, and enough observability to understand what your workloads are actually doing.</p>

<p>In the next post, I’ll move from infrastructure into running real Spark workloads and what that experience looks like in practice.</p>

<hr />

<p>Repository Reference</p>

<p>The complete Terraform configuration used in this article is available in <a href="https://github.com/luisgb14/zero-to-emr-serverless">GitHub</a>.</p>

<p>The repository includes the full infrastructure code, example configuration files, diagrams, and deployment guidance so you can reproduce the environment, experiment safely, or adapt it to your own workloads.</p>

<p>Feel free to explore the code, open issues, or reuse the setup as a starting point for building your own EMR Serverless development environments.</p>]]></content><author><name>Luis Benavides</name></author><category term="aws" /><category term="terraform" /><category term="infrastructure" /><category term="aws" /><category term="emr" /><category term="serverless" /><category term="data" /><category term="terraform" /><category term="infrastructure" /><summary type="html"><![CDATA[This Terraform setup provisions a practical EMR Serverless development environment on AWS — not just isolated resources, but a coherent workspace where engineers can actually experiment, iterate, and understand how EMR behaves in real conditions. It includes networking, storage, IAM roles, logging, and EMR Studio so developers can run Spark workloads without spending days wiring infrastructure manually.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://benlabs.dev/assets/images/og-image.png" /><media:content medium="image" url="https://benlabs.dev/assets/images/og-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What is AWS EMR?</title><link href="https://benlabs.dev/blog/2026/02/12/what-is-aws-emr/" rel="alternate" type="text/html" title="What is AWS EMR?" /><published>2026-02-12T00:00:00+00:00</published><updated>2026-02-12T00:00:00+00:00</updated><id>https://benlabs.dev/blog/2026/02/12/what-is-aws-emr</id><content type="html" xml:base="https://benlabs.dev/blog/2026/02/12/what-is-aws-emr/"><![CDATA[<h2 id="a-practical-note-before-we-start">A Practical Note Before We Start</h2>

<p>Before jumping into the theory, I want to share something from real-world experience. EMR is powerful, but getting started with it isn’t always straightforward. For many developers — even experienced ones — testing EMR workloads locally or in a safe dev environment can be challenging. The ecosystem is broad, the configurations can be nuanced, and understanding how all the moving parts connect sometimes takes longer than expected.</p>

<p>From an infrastructure and DevOps perspective, I’ve found that creating a reproducible environment with Infrastructure as Code makes a huge difference. In my case, Terraform allowed me to build a consistent, cost-controlled development setup where experimentation becomes practical instead of painful. In the second part of this series, I’ll walk through how I approached building that environment so you can test, learn, and iterate without unnecessary friction.</p>

<hr />

<p>Amazon EMR (Elastic MapReduce) is a managed big data platform from AWS designed to process and analyze large datasets at scale. It simplifies running distributed data processing frameworks such as Apache Spark, Hadoop, Hive, Presto, Flink, and others without requiring you to manually provision, configure, or maintain complex cluster infrastructure.</p>

<p>Instead of building and operating your own big data environment, EMR provides managed compute resources, integrations with AWS storage services like Amazon S3, automated scaling capabilities, and built-in monitoring. This allows teams to focus on extracting insights from data rather than managing infrastructure.</p>

<p>EMR can run in multiple deployment models depending on operational needs:</p>

<ul>
  <li><strong>EMR on EC2:</strong> Traditional cluster-based deployment where you control instance types, networking, scaling, and lifecycle.</li>
  <li><strong>EMR Serverless:</strong> Fully managed execution model where AWS handles infrastructure provisioning automatically and you pay only for compute consumed.</li>
  <li><strong>EMR on EKS:</strong> Integration with Kubernetes environments for organizations already operating container-based workloads.</li>
</ul>

<p>This flexibility makes EMR suitable for a wide range of data processing scenarios, from exploratory analytics to production-grade pipelines.</p>

<hr />

<h2 id="common-use-cases-for-aws-emr">Common Use Cases for AWS EMR</h2>

<h3 id="1-large-scale-data-processing-and-etl">1. Large-Scale Data Processing and ETL</h3>

<p>One of the most common uses of EMR is processing massive datasets for Extract, Transform, Load (ETL) workflows. Spark or Hive jobs can clean, transform, and aggregate data stored in S3, preparing it for analytics platforms, machine learning workflows, or downstream applications.</p>

<p>Typical examples include:</p>

<ul>
  <li>Log processing pipelines</li>
  <li>Data lake transformation workflows</li>
  <li>Batch aggregation jobs</li>
  <li>Data normalization before analytics ingestion</li>
</ul>

<hr />

<h3 id="2-interactive-analytics-and-data-exploration">2. Interactive Analytics and Data Exploration</h3>

<p>EMR supports interactive SQL engines such as Presto or Spark SQL, enabling data scientists and analysts to query large datasets directly without moving data into traditional databases.</p>

<p>This is often used for:</p>

<ul>
  <li>Ad hoc analytics</li>
  <li>Business intelligence reporting</li>
  <li>Data exploration during modeling phases</li>
  <li>Rapid investigation of operational data</li>
</ul>

<hr />

<h3 id="3-machine-learning-data-preparation">3. Machine Learning Data Preparation</h3>

<p>Machine learning workflows often require large-scale preprocessing before training models. EMR is frequently used to:</p>

<ul>
  <li>Prepare training datasets</li>
  <li>Perform feature engineering at scale</li>
  <li>Aggregate historical behavioral data</li>
  <li>Process structured and unstructured datasets</li>
</ul>

<p>Because it integrates easily with services like SageMaker, EMR often serves as the data preparation layer in ML pipelines.</p>

<hr />

<h3 id="4-streaming-data-processing">4. Streaming Data Processing</h3>

<p>With frameworks such as Apache Flink or Spark Streaming, EMR can handle near-real-time data processing scenarios, including:</p>

<ul>
  <li>Event stream analysis</li>
  <li>Fraud detection pipelines</li>
  <li>Real-time metrics aggregation</li>
  <li>IoT data ingestion</li>
</ul>

<p>This allows organizations to combine batch and streaming workloads in the same platform.</p>

<hr />

<h3 id="5-data-lake-architecture-support">5. Data Lake Architecture Support</h3>

<p>Many organizations use EMR as the compute layer on top of an Amazon S3 data lake. This approach separates storage from compute, providing flexibility, cost efficiency, and scalability.</p>

<p>Typical architecture patterns include:</p>

<ul>
  <li>S3 as durable storage</li>
  <li>EMR as distributed compute</li>
  <li>Glue Data Catalog for metadata</li>
  <li>Analytics tools querying processed datasets</li>
</ul>

<hr />

<p>In short, AWS EMR is designed to simplify large-scale data processing while offering multiple deployment options to balance control, cost, and operational overhead. It is particularly valuable when workloads involve large datasets, distributed processing, and integration with modern cloud data architectures.</p>]]></content><author><name>Luis Benavides</name></author><category term="aws" /><category term="data-engineering" /><category term="aws" /><category term="emr" /><category term="serverless" /><category term="data" /><summary type="html"><![CDATA[A Practical Note Before We Start]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://benlabs.dev/assets/images/og-image.png" /><media:content medium="image" url="https://benlabs.dev/assets/images/og-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Welcome — Why This Blog Exists</title><link href="https://benlabs.dev/blog/2026/02/07/welcome/" rel="alternate" type="text/html" title="Welcome — Why This Blog Exists" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://benlabs.dev/blog/2026/02/07/welcome</id><content type="html" xml:base="https://benlabs.dev/blog/2026/02/07/welcome/"><![CDATA[<p>Welcome. I’m glad you’re here.</p>

<p>I’ve been working in DevOps, cloud infrastructure, and automation for several years, building platforms, migrating workloads, troubleshooting production issues, and constantly learning how to do things better. This blog is simply a place to capture that journey — the wins, the mistakes, the lessons, and the ideas worth sharing.</p>

<p>Most of my day-to-day work revolves around AWS, Terraform, Docker, and Kubernetes, along with automation, platform engineering, CI/CD, and reliability practices. I’m particularly interested in building infrastructure that is practical, scalable, and maintainable — not just technically correct, but operationally sane. Recently, I’ve also started exploring how AI intersects with DevOps and cloud engineering, which is quickly becoming an exciting new layer in the ecosystem.</p>

<p>This isn’t meant to be a polished tutorial hub or marketing-style content. Think of it more as an engineering notebook: real experiences, architecture decisions, tooling experiments, lessons learned the hard way, and occasional opinions about where infrastructure and cloud technology are heading.</p>

<p>Outside of tech, life stays balanced thanks to video games, time with my family, and dogs — things that remind me that building cool technology should still be enjoyable and meaningful.</p>

<p>If anything you read here helps you solve a problem, learn something new, or see infrastructure from a slightly different angle, then the blog is doing exactly what it’s meant to do.</p>]]></content><author><name>Luis Benavides</name></author><category term="meta" /><category term="blog" /><summary type="html"><![CDATA[Welcome. I’m glad you’re here.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://benlabs.dev/assets/images/og-image.png" /><media:content medium="image" url="https://benlabs.dev/assets/images/og-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>