<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cloud & DevOps by Rohan]]></title><description><![CDATA[Cloud & DevOps by Rohan]]></description><link>https://rohanmatre.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Cloud &amp; DevOps by Rohan</title><link>https://rohanmatre.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 07 Sep 2026 21:27:32 GMT</lastBuildDate><atom:link href="https://rohanmatre.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Handling Schema Evolution in Contact Trace Records: Why We Added Lambda to Our Kinesis Firehose Pipeline]]></title><description><![CDATA[When building a real-time analytics pipeline for contact center data, getting the initial architecture up and running is only half the battle. Recently, we faced a classic maintenance hurdle: keeping ]]></description><link>https://rohanmatre.hashnode.dev/handling-schema-evolution-in-contact-trace-records-why-we-added-lambda-to-our-kinesis-firehose-pipeline</link><guid isPermaLink="true">https://rohanmatre.hashnode.dev/handling-schema-evolution-in-contact-trace-records-why-we-added-lambda-to-our-kinesis-firehose-pipeline</guid><category><![CDATA[AWS]]></category><category><![CDATA[Kinesis]]></category><category><![CDATA[lambda]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[cloud architecture]]></category><dc:creator><![CDATA[Rohan Matre]]></dc:creator><pubDate>Sun, 16 Aug 2026 01:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/17be1398-def2-492e-98bf-648e8d4ee811.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When building a real-time analytics pipeline for contact center data, getting the initial architecture up and running is only half the battle. Recently, we faced a classic maintenance hurdle: keeping our ingestion schema aligned with evolving JSON structures without breaking downstream processing.</p>
<p>Here is how we tackled the problem by introducing a dynamic transformation layer into our Amazon Connect and Kinesis pipeline.</p>
<h3>The Existing Architecture</h3>
<p>Our original pipeline was built to stream contact center metrics directly into our data lake for analytics.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/31605e55-247f-48bf-81f4-c1848357daa7.png" alt="" style="display:block;margin:0 auto" />

<h4>How the Data Flow Worked</h4>
<ol>
<li><p><strong>Amazon Connect:</strong> When a customer interacts with an agent, Amazon Connect generates a Contact Trace Record (CTR) detailing the interaction.</p>
</li>
<li><p><strong>Kinesis Data Streams:</strong> The CTR data is pushed to Kinesis Data Streams, protected using AWS KMS encryption.</p>
</li>
<li><p><strong>Kinesis Data Firehose:</strong> Firehose consumes the stream records to batch and prepare them for delivery.</p>
</li>
<li><p><strong>AWS Glue:</strong> Firehose uses AWS Glue for schema-related processing to understand the data structure.</p>
</li>
<li><p><strong>Apache Parquet Conversion:</strong> Firehose converts the processed records into columnar Apache Parquet format.</p>
</li>
<li><p><strong>Amazon S3:</strong> The resulting Parquet files are written to an Amazon S3 bucket.</p>
</li>
<li><p><strong>Databricks:</strong> Databricks reads the Parquet files from S3 for downstream analytics and processing.</p>
</li>
</ol>
<h3>The Problem: Fixed Schema in AWS Glue</h3>
<p>The pipeline itself was working, but we ran into a structural challenge as our contact flows evolved.</p>
<p>Our AWS Glue schema definition was fixed. When the incoming CTR payload structure changed—such as introducing a new attribute or modifying nested fields—the rigid schema could no longer keep up cleanly.</p>
<p>For instance, consider how a simple JSON payload change impacts a strict schema setup:</p>
<pre><code class="language-json">// Existing JSON Structure
{
  "fieldA": "valueA",
  "fieldB": "valueB"
}

// Evolved JSON Structure
{
  "fieldA": "valueA",
  "fieldB": "valueB",
  "fieldC": "valueC"
}
</code></pre>
<p>The issue isn't just that JSON schemas change; it is that a fixed Glue schema can quickly turn into a maintenance bottleneck. If incoming records contain new elements or altered structures, downstream formatting can fail or drop fields unless the schema definitions are manually or constantly updated. We needed a way to handle schema evolution more gracefully without constantly breaking our delivery pipeline.</p>
<h3>Our Proposed Architecture</h3>
<p>To solve this, we proposed introducing an AWS Lambda function between Kinesis Data Firehose and AWS Glue. This acts as a transformation and detection layer, allowing us to inspect and normalize incoming data structures dynamically before Glue processes them.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/47e2cb2b-51cf-4ff9-8df5-0f4a2538aace.png" alt="" style="display:block;margin:0 auto" />

<h4>Step-by-Step Through the New Flow</h4>
<ol>
<li><p><strong>Amazon Connect:</strong> Generates the CTR for customer-agent interactions.</p>
</li>
<li><p><strong>Kinesis Data Streams:</strong> Ingests the CTR data, secured with KMS encryption.</p>
</li>
<li><p><strong>Kinesis Data Firehose:</strong> Receives the stream records and prepares them for delivery.</p>
</li>
<li><p><strong>Lambda Transformation Layer:</strong> Instead of sending raw, unpredictable payloads straight to Glue, Firehose passes the records through an AWS Lambda function. Lambda examines the incoming record, identifies the relevant object ID and JSON structure, and prepares the payload so downstream services can handle it properly.</p>
</li>
<li><p><strong>AWS Glue:</strong> Receives the dynamically prepared information to provide the correct schema mapping.</p>
</li>
<li><p><strong>Firehose Data Conversion:</strong> Armed with the proper schema handling, Firehose converts the data into Apache Parquet format.</p>
</li>
<li><p><strong>Amazon S3:</strong> Stores the finalized Parquet files.</p>
</li>
<li><p><strong>Databricks:</strong> Consumes the Parquet data from S3 for downstream analytics.</p>
</li>
</ol>
<h3>Original vs. Proposed Architecture</h3>
<ul>
<li><p><strong>Original:</strong> <code>Connect → CTR → Kinesis Data Streams → Firehose → Glue → Parquet → S3 → Databricks</code></p>
<ul>
<li><em>Limitation:</em> Relies entirely on a fixed, hard-coded Glue schema.</li>
</ul>
</li>
<li><p><strong>Proposed:</strong> <code>Connect → CTR → Kinesis Data Streams → Firehose → Lambda → Glue → Firehose → Parquet → S3 → Databricks</code></p>
<ul>
<li><em>Improvement:</em> Introduces a flexible Lambda transformation layer to handle schema variations before Glue processing.</li>
</ul>
</li>
</ul>
<h3>Benefits and Trade-offs</h3>
<p>Introducing Lambda into the middle of a Firehose delivery stream gave us much better control over data payloads, but it came with trade-offs worth considering:</p>
<ul>
<li><p><strong>Pros:</strong></p>
<ul>
<li><p>Greater flexibility when handling evolving JSON structures.</p>
</li>
<li><p>Reduced maintenance overhead for rigid, static schemas.</p>
</li>
<li><p>Better visibility into record payloads before storage conversion.</p>
</li>
</ul>
</li>
<li><p><strong>Cons &amp; Considerations:</strong></p>
<ul>
<li><p>Additional architectural complexity with a compute layer inside the streaming pipeline.</p>
</li>
<li><p>Lambda execution latency and concurrency limits to monitor.</p>
</li>
<li><p>Added monitoring and error-handling requirements for transformation failures.</p>
</li>
</ul>
</li>
</ul>
<h3>Key Takeaways</h3>
<p>Rigid schemas are often the silent bottleneck of real-time data pipelines. By introducing an AWS Lambda transformation layer between Kinesis Data Firehose and AWS Glue, we decoupled our ingestion pipeline from strict schema limitations, making our contact center data lake much more resilient to change.</p>
]]></content:encoded></item><item><title><![CDATA[Scaling Event-Driven Architecture: Implementing Amazon Connect Outbound Campaign Enrichment with Terraform]]></title><description><![CDATA[As a Platform Engineering team, our primary focus isn't just provisioning individual AWS resources. We translate high-level application requirements into reusable, maintainable, and repeatable infrast]]></description><link>https://rohanmatre.hashnode.dev/scaling-event-driven-architecture-implementing-amazon-connect-outbound-campaign-enrichment-with-terraform</link><guid isPermaLink="true">https://rohanmatre.hashnode.dev/scaling-event-driven-architecture-implementing-amazon-connect-outbound-campaign-enrichment-with-terraform</guid><category><![CDATA[AWS]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Platform Engineering ]]></category><category><![CDATA[lambda]]></category><category><![CDATA[SQS]]></category><category><![CDATA[sns]]></category><category><![CDATA[Kinesis]]></category><dc:creator><![CDATA[Rohan Matre]]></dc:creator><pubDate>Sat, 15 Aug 2026 07:09:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/a4c43b0e-7613-466d-a64a-9b39d712a597.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a Platform Engineering team, our primary focus isn't just provisioning individual AWS resources. We translate high-level application requirements into reusable, maintainable, and repeatable infrastructure patterns that can be rolled out consistently across multiple AWS accounts and environments.</p>
<p>Recently, our application teams required an update to our Amazon Connect Outbound Campaigns architecture. What started as a straightforward event pipe needed to evolve into a multi-stage, fan-out event-processing pipeline for contact ID and attribute enrichment.</p>
<p>In this post, I will walk through how we approached this architectural shift, mapped out the components, implemented them using our tiered Terraform repository structure, and managed the complexities of multi-account deployments.</p>
<h2>Table of Contents</h2>
<ol>
<li><p>Introduction</p>
</li>
<li><p>Our Terraform Repository Architecture</p>
</li>
<li><p>The Existing Amazon Connect Outbound Architecture</p>
</li>
<li><p>Why the Architecture Needed to Change</p>
</li>
<li><p>The New Architecture</p>
</li>
<li><p>Why SNS + SQS Was Introduced</p>
</li>
<li><p>Terraform Implementation</p>
</li>
<li><p>Multi-Account Deployment</p>
</li>
<li><p>Infrastructure as Code vs. Console-Based Infrastructure</p>
</li>
<li><p>Engineering Considerations</p>
</li>
<li><p>Lessons Learned as a Platform Engineer</p>
</li>
<li><p>Key Takeaways</p>
</li>
</ol>
<h2>1. Introduction</h2>
<p>When product requirements shift, infrastructure must adapt without introducing drift or deployment friction. For a platform engineer, handling a request like "we need to enrich contact attributes before they hit downstream systems" means looking beyond the immediate AWS console clicks. We have to design an infrastructure building block that is clean, secure, least-privileged, and easily stampable across staging, production, and multiple customer-facing AWS accounts.</p>
<h2>2. Our Terraform Repository Architecture</h2>
<p>To maintain consistency and avoid copy-pasting Terraform code across dozens of repositories, our team enforces a structured, tiered repository hierarchy:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/3581c53f-b1cb-41ee-b93d-9869dd8d1e72.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Terraform Public Modules:</strong> Base, upstream modules defining core building blocks.</p>
</li>
<li><p><strong>JFrog Organization Repository:</strong> Internally versioned and vetted modules stored in our organization's JFrog registry.</p>
</li>
<li><p><strong>Wrapper Repository:</strong> Environment- or pattern-opinionated wrappers that bind internal standards to modules.</p>
</li>
<li><p><strong>Service-Specific Repository:</strong> The actual implementation repo (e.g., Connect, Networking, Routing repositories) where application-specific infrastructure is composed using our wrappers and modules.</p>
</li>
</ul>
<h2>3. The Existing Amazon Connect Outbound Architecture</h2>
<p>Previously, our Amazon Connect outbound campaign flow was intentionally simple:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/4244bdb1-8d10-4296-9c8b-6d2fcd1c9d85.png" alt="" style="display:block;margin:0 auto" />

<p>An <strong>Amazon EventBridge Pipe</strong> directly connected outbound campaign events from Amazon Connect to an <strong>Amazon Kinesis Data Stream</strong>. This worked well for basic streaming requirements where raw events required zero intermediate transformation or validation.</p>
<h2>4. Why the Architecture Needed to Change</h2>
<p>New requirements introduced a need for <strong>Contact ID and Contact Attribute Processing</strong>.</p>
<p>The direct flow from EventBridge to Kinesis was no longer sufficient because upstream contact data needed to be intercepted, parsed, enriched, and fanned out to distinct consumers (such as user profile updaters and secondary logging or processing paths) before reaching the final data stream.</p>
<h2>5. The New Architecture</h2>
<p>To support enrichment and parallel consumer workflows, we expanded the pipeline into an event-driven fan-out pattern.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/7818f098-0614-44e9-8b31-bd50f5605b9a.png" alt="" style="display:block;margin:0 auto" />

<h3>Service Responsibilities:</h3>
<ul>
<li><p><strong>Amazon Connect:</strong> Generates outbound contact campaign events.</p>
</li>
<li><p><strong>Amazon EventBridge:</strong> Receives outbound events and routes them to the initial processing handler.</p>
</li>
<li><p><strong>Enrichment Lambda:</strong> Processes incoming contact IDs and contact attributes.</p>
</li>
<li><p><strong>Amazon SNS:</strong> Acts as a fan-out publishing hub for the enriched messages.</p>
</li>
<li><p><strong>SQS #1 &amp; SQS #2:</strong> Decouple the SNS publisher from downstream consumers, offering buffering and queue isolation.</p>
</li>
<li><p><strong>Update Profile Lambda:</strong> Consumes messages from SQS #1 to update contact profiles before pushing records into Kinesis.</p>
</li>
<li><p><strong>Update Lambda:</strong> Consumes messages from SQS #2 to perform additional contact-related processing.</p>
</li>
<li><p><strong>Kinesis Data Streams:</strong> Receives the final processed events for downstream ingestion.</p>
</li>
</ul>
<h2>6. Why SNS + SQS Was Introduced</h2>
<p>Introducing <strong>Amazon SNS</strong> combined with multiple <strong>Amazon SQS</strong> queues provides key architectural advantages:</p>
<ul>
<li><p><strong>Fan-out Capability:</strong> A single enriched event from the Lambda function can be broadcast to multiple independent subscribers simultaneously via SNS.</p>
</li>
<li><p><strong>Decoupling &amp; Asynchronous Processing:</strong> Producers do not block or wait for consumers to finish processing, shielding upstream systems from downstream latency or transient outages.</p>
</li>
<li><p><strong>Failure Isolation &amp; Retries:</strong> If a consumer Lambda fails while processing a batch from an SQS queue, messages are retained safely in the queue according to visibility timeout configurations, preventing data loss.</p>
</li>
</ul>
<h2>7. Terraform Implementation</h2>
<p>Translating this architecture into our service-specific repository means orchestrating multiple AWS resources cleanly. Rather than writing raw, repetitive resource definitions, we leverage our internal wrappers.</p>
<p>An illustrative example of how we compose parts of this pipeline in Terraform:</p>
<pre><code class="language-plaintext">module "enrichment_lambda" {
  source = "app.jfrog.io/our-org/lambda-wrapper/aws"
  version = "1.2.0"

  function_name = "connect-enrichment-${var.environment}"
  handler       = "index.handler"
  runtime       = "nodejs18.x"
  role_arn      = aws_iam_role.lambda_execution.arn
  
  environment_variables = {
    SNS_TOPIC_ARN = aws_sns_topic.enrichment.arn
  }
}

resource "aws_sns_topic" "enrichment" {
  name = "connect-enriched-events-${var.environment}"
}

resource "aws_sqs_queue" "profile_update" {
  name = "connect-profile-update-${var.environment}"
}

resource "aws_sns_topic_subscription" "sqs_subscription" {
  topic_arn = aws_sns_topic.enrichment.arn
  protocol  = "sqs"
  endpoint  = aws_sqs_queue.profile_update.arn
}
</code></pre>
<p>Other components managed within the repository include EventBridge rule targets, SQS-to-Lambda event source mappings, least-privilege IAM roles, and Kinesis stream configurations.</p>
<h2>8. Multi-Account Deployment</h2>
<p>Because this infrastructure pattern runs across multiple AWS accounts and environments (such as staging and production), hardcoding names, ARNs, or account IDs is prohibited.</p>
<ul>
<li><p><strong>Environment-Specific Variables:</strong> Inputs like environment tags, naming prefixes, and scaling thresholds are passed down via Terraform variables (<code>var.environment</code>).</p>
</li>
<li><p><strong>Dynamic ARNs:</strong> IAM policies and SNS subscription endpoints dynamically reference resource attributes (<code>aws_sns_topic.enrichment.arn</code>) rather than relying on static strings.</p>
</li>
<li><p><strong>Provider Configuration:</strong> Multi-account provider aliasing ensures our wrapper modules stamp identical architecture reliably into whichever target AWS account our pipeline authenticates against.</p>
</li>
</ul>
<h2>9. Infrastructure as Code vs. Console-Based Infrastructure</h2>
<p>Managing an event-driven topology of this size manually via the AWS Console introduces significant operational risks:</p>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Manual AWS Console Configuration</strong></p></td><td><p><strong>Reusable Terraform Infrastructure</strong></p></td></tr><tr><td><p>Prone to human click-ops error</p></td><td><p>Version-controlled and peer-reviewed</p></td></tr><tr><td><p>Difficult to replicate across accounts</p></td><td><p>Stampable across multiple environments</p></td></tr><tr><td><p>High configuration drift over time</p></td><td><p>Declarative state consistency</p></td></tr><tr><td><p>Hard to audit changes</p></td><td><p>Clear git history and pull request workflows</p></td></tr></tbody></table>

<h2>10. Engineering Considerations</h2>
<p>When deploying this architecture, our Platform Engineering team pays close attention to several production criteria:</p>
<ul>
<li><p><strong>IAM &amp; Least-Privilege:</strong> We strictly scope permissions—ensuring EventBridge can only invoke the Enrichment Lambda, Lambda can only publish to the specific SNS topic, and SQS queues only permit messages from authorized SNS topics or trigger their designated consumer Lambdas.</p>
</li>
<li><p><strong>Reliability:</strong> <em>A consideration for production:</em> Configure appropriate SQS visibility timeouts that exceed the maximum execution timeout of the downstream consumer Lambdas, and set up Dead-Letter Queues (DLQs) to catch poisoned messages.</p>
</li>
<li><p><strong>Observability:</strong> CloudWatch metrics on SQS queue depths (e.g., <code>ApproximateNumberOfMessagesVisible</code>) and Lambda error rates are critical for spotting processing bottlenecks across the pipeline.</p>
</li>
<li><p><strong>Scalability:</strong> The asynchronous handoff between SNS and SQS ensures spikes in Amazon Connect outbound call volume are safely buffered without overwhelming consumer functions.</p>
</li>
</ul>
<h2>11. Lessons Learned as a Platform Engineer</h2>
<ul>
<li><p><strong>Requirements evolve:</strong> Event-driven architectures often start simple (like a direct pipe) and grow complex as business logic requires intermediate data enrichment. Building modularly from the start makes these pivots easier.</p>
</li>
<li><p><strong>Abstraction must balance reusability:</strong> When wrapping modules for application teams, hide unnecessary complexity while exposing critical tuning parameters (like timeout and memory sizes for Lambda).</p>
</li>
<li><p><strong>Consistency matters:</strong> Multi-account deployments amplify small configuration mistakes; enforcing infrastructure changes through code review protects every environment equally.</p>
</li>
</ul>
<h2>12. Key Takeaways</h2>
<ul>
<li><p><strong>Decoupling scales:</strong> Inserting SNS and SQS between producers and consumers protects your pipelines from downstream failures and traffic spikes.</p>
</li>
<li><p><strong>Platform engineering creates leverage:</strong> Reusable Terraform wrappers prevent teams from reinventing event-driven patterns for every new account.</p>
</li>
<li><p><strong>Keep code DRY:</strong> Organizing Terraform configurations into a structured repository hierarchy makes maintaining multi-account AWS architectures sustainable.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Building a Secure, Human-Approved Automated EC2 Alert Remediation System]]></title><description><![CDATA[Picture this: It's 3 AM. A critical alert fires because an EC2 instance is running out of memory or a process went rogue. Traditionally, an engineer wakes up, finds their laptop, logs in, SSHs into th]]></description><link>https://rohanmatre.hashnode.dev/building-a-secure-human-approved-automated-ec2-alert-remediation-system</link><guid isPermaLink="true">https://rohanmatre.hashnode.dev/building-a-secure-human-approved-automated-ec2-alert-remediation-system</guid><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[finops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Rohan Matre]]></dc:creator><pubDate>Thu, 16 Jul 2026 04:48:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/36da4d00-85a6-4e07-8756-25f7411c3427.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Picture this: It's 3 AM. A critical alert fires because an EC2 instance is running out of memory or a process went rogue. Traditionally, an engineer wakes up, finds their laptop, logs in, SSHs into the server, and runs a restart command.</p>
<p>What if you could automate the entire triage and fix, keep a strict audit trail, completely ban SSH, and still keep a human in the loop for safety—all via Microsoft Teams?</p>
<p>Let’s look at a production-grade, multi-region architecture designed to do exactly that.</p>
<h2>The Core Design Principles</h2>
<p>Before looking at the workflow, this architecture is anchored on a few non-negotiable security and operational pillars:</p>
<ul>
<li><p><strong>No SSH Allowed:</strong> Absolutely zero direct SSH access to the workloads. All actions happen securely via AWS Systems Manager (SSM).</p>
</li>
<li><p><strong>Human-Approved Actions:</strong> Automation doesn't mean blindly running scripts. Critical actions require an authorized engineer to click a button.</p>
</li>
<li><p><strong>Least Privilege &amp; Cross-Account Isolation:</strong> The system isolates the remediation logic inside a central Automation Control Plane account, assuming minimal IAM roles to interact with target workload accounts.</p>
</li>
<li><p><strong>Audit Everything:</strong> Every single alert, payload validation, human click, and command output is logged immutably.</p>
</li>
</ul>
<h2>Architecture Blueprint</h2>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/747bd101-29f5-40aa-a6fb-580c02a84e96.png" alt="" style="display:block;margin:0 auto" />

<h3>Step-by-Step Workflow Breakdown</h3>
<p>The entire lifecycle—from detecting a fault to fixing it—is broken down into distinct, decoupled phases managed seamlessly by AWS Step Functions.</p>
<h3>1. Alert Detection &amp; Secure Ingress</h3>
<ul>
<li><p><strong>Detection:</strong> Prometheus and Node Exporters continuously gather metrics across the EC2 fleet. When an anomaly or threshold is met, Grafana evaluates the alert rule.</p>
</li>
<li><p><strong>Ingress:</strong> Grafana sends a secure HTTPS Webhook payload. This hits <strong>CloudFront</strong> (global entry) and passes through <strong>AWS WAF</strong> (handling rate limiting and IP allowlisting) before triggering an <strong>API Gateway HTTP API</strong>.</p>
</li>
</ul>
<h3>2. Validation &amp; Parsing</h3>
<ul>
<li><p><strong>Handler Lambda:</strong> Validates the payload’s timestamp, checks a cryptographic nonce to prevent replay attacks, verifies the HMAC signature, and generates a unified correlation ID.</p>
</li>
<li><p><strong>Alert Parser Lambda:</strong> If valid, the Step Function workflow starts. The alert parser extracts critical metadata: <code>instance_id</code>, <code>private_ip</code>, <code>region</code>, <code>account_id</code>, and metric details.</p>
</li>
<li><p><strong>Instance Resolver Lambda:</strong> Maps the alert data to the exact EC2 target, calling <code>DescribeInstances</code> if it only has a private IP to grab instance tags, state, and OS details.</p>
</li>
</ul>
<h3>3. Guardrails &amp; The Human Element</h3>
<ul>
<li><p><strong>Security &amp; Guardrail Validation:</strong> Before moving an inch forward, a dedicated safety check runs. Is this a protected instance? Is the requested action allowed? Is the SSM Agent online?</p>
</li>
<li><p><strong>Orchestration (Step Functions):</strong> AWS Step Functions acts as the brain, managing the logic gates and triggering a pause to wait for human interaction.</p>
</li>
<li><p><strong>Human Approval via Teams:</strong> A Teams Bot sends a highly descriptive <strong>Adaptive Card</strong> to an authorized Microsoft Teams channel. The card displays live alert details alongside clear interactive choices: <strong>Approve, Reject, Ignore, Restart Service, or Kill Process</strong>. If a timeout occurs, it defaults to a safe Auto-Reject status.</p>
</li>
</ul>
<h3>4. Cross-Account Execution</h3>
<ul>
<li><p><strong>Executor Lambda:</strong> Once an operator clicks "Approve", the workflow resumes. The Executor Lambda assumes a cross-account IAM role into the specific workload account target.</p>
</li>
<li><p><strong>AWS Systems Manager (SSM):</strong> Rather than manual execution, it leverages approved, pre-defined SSM Documents to run the remediation script (<code>Run Command</code>) with strict concurrency control.</p>
</li>
</ul>
<h3>5. Collection, Notification &amp; Audit</h3>
<ul>
<li><p><strong>Reporter Lambda:</strong> Collects the execution logs and command results from SSM, parsing whether the remediation succeeded or failed.</p>
</li>
<li><p><strong>Teams Notification:</strong> Sends a final success/failure summary back to the Microsoft Teams channel, noting execution time and next steps.</p>
</li>
<li><p><strong>Auditor Lambda:</strong> Writes the complete history to an immutable <strong>S3 bucket (with Object Lock)</strong>, updates a central DynamoDB table, and closes out the incident.</p>
</li>
</ul>
<h2>The Centralized Logging &amp; Monitoring Layer</h2>
<p>To ensure compliance and continuous debugging, every single phase pipes data into a centralized observability stack:</p>
<ul>
<li><p><strong>CloudWatch Logs &amp; Alarms:</strong> Centralizes logs from all Lambda functions and monitors for execution errors or timeouts.</p>
</li>
<li><p><strong>CloudTrail:</strong> Audits every single API call and cross-account STS role assumption.</p>
</li>
<li><p><strong>Amazon Athena &amp; QuickSight:</strong> Allows the operations team to run analytical SQL queries over historical audit data and view remediation success metrics via interactive dashboards.</p>
</li>
<li><p><strong>AWS X-Ray:</strong> Provides end-to-end distributed tracing across the API Gateway, Step Functions, and various Lambda microservices.</p>
</li>
</ul>
<h2>Conclusion</h2>
<p>By shifting away from traditional SSH runbooks and moving towards an event-driven, microservice-based remediation model, you get the best of both worlds: rapid incident response times and airtight enterprise security. Your engineering team stays in full control right from their chat client, and your infrastructure remains a hardened fortress.</p>
<p><em>What are your thoughts on using interactive chat cards for infrastructure remediation? Let me know in the comments below!</em></p>
]]></content:encoded></item><item><title><![CDATA[Building a Production-Ready AWS Billing Monitoring System with AWS Lambda, Cost Explorer, Amazon Bedrock & Microsoft Teams]]></title><description><![CDATA[Every cloud engineer has a shared nightmare: waking up to a Slack or Microsoft Teams notification showing a massive, unexpected spike in the monthly AWS bill. It usually follows a predictable pattern—]]></description><link>https://rohanmatre.hashnode.dev/building-a-production-ready-aws-billing-monitoring-system-with-aws-lambda-cost-explorer-amazon-bedrock-microsoft-teams</link><guid isPermaLink="true">https://rohanmatre.hashnode.dev/building-a-production-ready-aws-billing-monitoring-system-with-aws-lambda-cost-explorer-amazon-bedrock-microsoft-teams</guid><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[finops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Rohan Matre]]></dc:creator><pubDate>Thu, 09 Jul 2026 04:48:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/a8797026-e3f6-4a4b-bb9f-97e0b611c9d7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every cloud engineer has a shared nightmare: waking up to a Slack or Microsoft Teams notification showing a massive, unexpected spike in the monthly AWS bill. It usually follows a predictable pattern—a developer spins up an unmanaged g5.4xlarge EC2 instance for an LLM experiment and forgets about it over the weekend, or a recursive Lambda function goes rogue, invoking itself millions of times an hour.</p>
<p>By the time the standard AWS monthly budget alert triggers, the damage is already in the thousands of dollars.</p>
<p>While AWS provides native solutions like AWS Cost Anomaly Detection, these tools often act as a black box. They use complex, proprietary machine learning models that can take days to train, sometimes missing sudden, sharp spikes in lower-spending accounts or generating a flood of alert fatigue for minor deviations.</p>
<p>I wanted something different for our production environment: a lightweight, serverless, deterministic, and AI-supplemented billing monitor built entirely using native AWS primitives and our existing observability stack.</p>
<p><strong>The Engineering Guardrails</strong><br />When mapping out this Proof of Concept (POC), I enforced strict constraints to keep our operational footprint near zero:<br /><strong>1. Fully Serverless &amp; Stateless:</strong> No databases, no persistent queues, and no long-running container clusters.<br /><strong>2. Zero Added Infrastructure Drag:</strong> Absolutely NO API Gateway, DynamoDB, S3 buckets, RDS databases, AWS Step Functions, or OpenSearch clusters. The system must run entirely within ephemeral execution contexts.<br /><strong>3. Deterministic Logic, AI Explanation:</strong> AI should never decide if an anomaly happened—that is a math problem. Instead, AI should only be invoked to explain the anomaly after the math flags it. This keeps inference costs trivial.<br /><strong>4. Native Observability Integration:</strong> It must feed directly into our existing self-hosted Grafana instance and emit standard CloudWatch metrics.<br /><strong>5. Actionable ChatOps:</strong> Notifications must land directly in Microsoft Teams via Modern Workflows using rich Adaptive Cards, bypassing old SNS-to-email pipelines.</p>
<p>Here is the engineering story of how I designed, implemented, debugged, and rolled out this intelligent cloud financial operations (FinOps) guardrail.</p>
<p><strong>The System Architecture</strong><br />A reliable production monitoring tool must be simple. By stripping away extraneous structural layers, we eliminated cold starts, complex state machines, and data synchronization issues.<br />The entire data and control flow executes within a single daily cron cycle:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a3f43bbcab386de33a995cb/19f49fd7-2421-4deb-9ad6-43a02b144eea.png" alt="" style="display:block;margin:0 auto" />

<h3>Deep-Dive: Service Responsibilities</h3>
<p><strong>1. Amazon EventBridge Scheduler</strong></p>
<p>Acts as my reliable, serverless heartbeat. It triggers my central Lambda function daily at a designated time (e.g., 06:00 UTC), ensuring I calculate costs as soon as the previous day’s bill stabilizes.</p>
<p><strong>2. AWS Lambda (Core Engine)</strong></p>
<p>The single stateless computing core written in clean, modular Python. It manages the orchestration: retrieving data from Cost Explorer, calculating anomalies, sending telemetry to CloudWatch, invoking Bedrock if necessary, and assembling the payload for Microsoft Teams.</p>
<p><strong>3. AWS Cost Explorer API</strong></p>
<p>The source of truth for all cost and usage anomalies. I query it dynamically to extract the last 7 days of raw spending data, broken down by individual AWS services.</p>
<p><strong>4. CloudWatch Metrics &amp; Logs</strong></p>
<p>Instead of standing up a dedicated database to track spending history, I offload persistence entirely to CloudWatch Custom Metrics. Historical metrics provide the long-term trend data, while structured CloudWatch Logs store the raw JSON strings for auditability.</p>
<p><strong>5. Amazon Bedrock (Anthropic Claude 3 Haiku)</strong></p>
<p>My context-aware on-call assistant. When an anomaly is confirmed by my deterministic math formulas, Bedrock evaluates the service breakdown and writes a natural-language brief explaining exactly which service is driving the spike.</p>
<p><strong>6. Microsoft Teams Workflow</strong></p>
<p>My alerting destination. I use the updated Office 365 Workflows engine to accept incoming JSON webhooks and render highly structured, readable, color-coded Adaptive Cards directly inside my engineering channels.</p>
<p><strong>7. Existing Self-Hosted Grafana</strong></p>
<p>The analytical lens. Rather than adding a new dashboarding engine, I use the native Amazon CloudWatch plugin inside my pre-existing Grafana server to construct beautiful executive overviews and technical trend panels.</p>
<h2>The Business Logic: Dual-Condition Anomaly Detection</h2>
<p>A naive billing alert simply triggers when today's cost is higher than yesterday's. This is an operational anti-pattern that creates constant false alarms.</p>
<p>For instance, if your cost jumps from $5 to $10 due to a routine automated backup cycle, that is a <strong>100% increase</strong>, but it is completely insignificant in terms of real budget impact. Conversely, if your infrastructure steadily scales up over a week, comparing today only against yesterday might mask a massive cumulative spike.</p>
<p>To build a reliable indicator, my system requires <strong>both</strong> of the following conditions to evaluate to <code>True</code> before raising an alarm:</p>
<h3>Condition 1: Day-over-Day (DoD) Percentage Variance</h3>
<p>This measures sudden, immediate cost explosions between today and yesterday.</p>
<h3>Condition 2: 5-Day Rolling Average Variance</h3>
<p>This ensures that today's spike is a significant deviation from the baseline established over the previous work week.</p>
<h3>The Decision Matrix (Alert = Condition 1 AND Condition 2)</h3>
<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Condition 1 (DoD ≥ 20%)</strong></p></td><td><p><strong>Condition 2 (5-Day Avg ≥ 20%)</strong></p></td><td><p><strong>Operational Status</strong></p></td><td><p><strong>System Action</strong></p></td></tr><tr><td><p><strong>False</strong></p></td><td><p><strong>False</strong></p></td><td><p>Nominal Baseline</p></td><td><p>Emit standard metrics; terminate gracefully.</p></td></tr><tr><td><p><strong>True</strong></p></td><td><p><strong>False</strong></p></td><td><p>Short-term Blip (e.g., normal post-weekend cleanup)</p></td><td><p>Emit metrics; skip AI and alerts (Avoids False Positives).</p></td></tr><tr><td><p><strong>False</strong></p></td><td><p><strong>True</strong></p></td><td><p>Slow, creeping cost climb</p></td><td><p>Emit metrics; skip AI and alerts.</p></td></tr><tr><td><p><strong>True</strong></p></td><td><p><strong>True</strong></p></td><td><p><strong>True Anomaly Detected</strong></p></td><td><p><strong>Invoke Bedrock, write logs, fire Teams alert.</strong></p></td></tr></tbody></table>

<h3>Production Guardrail: Divide-by-Zero Protection</h3>
<p>In newly provisioned cloud environments, sandbox accounts, or low-traffic regions, yesterday's cost or the 5-day baseline average might be exactly <code>$0.00</code>. Mathematically, dividing by zero causes unhandled execution exceptions (<code>ZeroDivisionError</code>), crashing the execution flow.</p>
<p>Our core detection logic implements an explicit protection policy:</p>
<pre><code class="language-python">def calculate_percentage_variance(current_val: float, baseline_val: float) -&gt; float:
    if baseline_val &lt;= 0:
        return 100.0 if current_val &gt; 0 else 0.0
    return ((current_val - baseline_val) / baseline_val) * 100.0
</code></pre>
<ul>
<li><p>If the baseline is <code>$0.00</code> and current spending jumps to any positive value (e.g., <code>$15.00</code>), it represents a new cost center, triggering a <strong>100%</strong> variance jump.</p>
</li>
<li><p>If both baseline and current spending are <code>$0.00</code>, the variance is tracked as <strong>0%</strong>.</p>
</li>
</ul>
<h2>Project Directory &amp; Module Architecture</h2>
<p>To ensure this codebase remains clean, testable, and maintainable, we decoupled every logical boundary into dedicated files.</p>
<pre><code class="language-plaintext">billing-monitor/
├── lambda_function.py        # Central Lambda Orchestrator &amp; Handler
├── config.py                 # Environment Variables &amp; Hardcoded Thresholds
├── logger.py                 # Structured Global CloudWatch JSON Logger
├── cost_explorer.py          # Wrapper for AWS Cost Explorer Boto3 API
├── anomaly_detector.py       # Pure Deterministic Math Engine
├── cloudwatch_metrics.py     # Custom Metrics Dispatcher
├── bedrock_service.py        # GenAI Prompt &amp; Context Processor
├── teams_notifier.py         # HTTP Webhook Handler for Teams Adaptive Cards
├── tests/
│   └── test_detector.py      # Local Validation Matrix &amp; Mock Testing Unit
└── grafana/
    └── dashboard.json        # Production-ready Dashboard Definition Export
</code></pre>
<h3>Module Breakdown</h3>
<ul>
<li><p><code>lambda_</code><a href="http://function.py"><code>function.py</code></a>: The system gateway. It boots up, triggers the data collection lifecycle, balances states, catches top-level errors, and cleans up execution dependencies.</p>
</li>
<li><p><a href="http://config.py"><code>config.py</code></a>: Single source of truth for runtime constants (e.g., anomaly thresholds, Bedrock model definitions, Teams webhook URLs).</p>
</li>
<li><p><a href="http://logger.py"><code>logger.py</code></a>: Standardizes formatting so that all outputs are generated as searchable JSON blocks in CloudWatch Logs.</p>
</li>
<li><p><code>cost_</code><a href="http://explorer.py"><code>explorer.py</code></a>: Abstracts complex <code>boto3</code> parameters into a clean, direct interface that returns pure dictionary maps.</p>
</li>
<li><p><code>anomaly_</code><a href="http://detector.py"><code>detector.py</code></a>: A pure, dependency-free module containing our mathematical variance algorithms and decision matrices.</p>
</li>
<li><p><code>cloudwatch_</code><a href="http://metrics.py"><code>metrics.py</code></a>: Handles high-velocity metric construction, packing multiple dimensions for optimal data ingestion.</p>
</li>
<li><p><code>bedrock_</code><a href="http://service.py"><code>service.py</code></a>: Contains the prompt engineering blocks and structures payload variables for safe interactions with LLM endpoints.</p>
</li>
<li><p><code>teams_</code><a href="http://notifier.py"><code>notifier.py</code></a>: Prepares the structural JSON styling requirements of Microsoft Teams Workflows.</p>
</li>
</ul>
<p><strong>Production Alerting and Alarms</strong><br />Relying entirely on a notification script means you risk missing errors if the system itself breaks down. To ensure full coverage, configure the following native CloudWatch Alarms alongside your dashboard metrics:</p>
<ol>
<li><strong>Hard Anomaly Alarm Metric: AnomalyStatus</strong></li>
</ol>
<ul>
<li><p><strong>Metric:</strong> <code>AnomalyStatus</code></p>
</li>
<li><p><strong>Threshold:</strong> <code>&gt; 0</code> for 1 consecutive period.</p>
</li>
<li><p><strong>Action:</strong> If our mathematical calculation detects an anomaly, this alarm changes state immediately. It serves as a critical operational fallback, with notifications routed to an SNS queue or PagerDuty.</p>
</li>
</ul>
<p><strong>2. Monitoring Pipeline Error Fail-Safe Metric:</strong></p>
<ul>
<li><p><strong>Metric:</strong> <code>Errors</code> under namespace <code>AWS/Lambda</code> for dimension <code>FunctionName: BillingMonitor</code>.</p>
</li>
<li><p><strong>Threshold:</strong> <code>&gt;= 1</code> for 1 evaluation loop.</p>
</li>
<li><p><strong>Action:</strong> Triggers an alert if our custom python script throws an unhandled exception, ensuring visibility if API changes or runtime crashes break our monitoring system.</p>
</li>
</ul>
<h2>Production Edge Cases and Challenges</h2>
<p>Building a production-ready system means anticipating where things will break in the real world. During our testing phases, we ran into several interesting edge cases that forced us to refine our design.</p>
<p><strong>Challenge 1: Bedrock Claude 3 JSON Formatting Deviations</strong></p>
<p><strong>The Issue:</strong> LLMs can be unpredictable. Despite specifying strict output constraints, Claude occasionally wrapped its responses in unwanted markdown blocks.</p>
<h3>2. Multi-Account Aggregation Strategies</h3>
<p>In complex organizational settings, deploying separate monitoring functions across dozens of distinct child accounts quickly becomes a management nightmare.</p>
<p>Instead, leverage AWS Organizations to deploy this system a single time inside your centralized <strong>Billing or Management Master Account</strong>. From there, modify your Cost Explorer ingestion queries to group data fields by the <code>LINKED_ACCOUNT</code> dimension. This allows you to track and segment anomalies across your entire organization from a single, centralized point of control.</p>
<p><strong>Conclusion and Next Steps</strong></p>
<p>By designing with strict constraints, we built an intelligent, serverless cloud financial monitor that keeps operational overhead near zero.</p>
<p>By avoiding complex state databases, API gateways, and web application servers, we eliminated common security vectors and maintenance headaches.</p>
<p>Our dual-condition verification logic acts as a dependable mathematical filter, ensuring we only invoke Bedrock's generative text models when a true anomaly is confirmed. This keeps our monthly AI inference cost to pennies, while delivering clear, context-aware engineering summaries directly to our team via Microsoft Teams.</p>
<p>The system is fully auditable, emits native CloudWatch metrics, and plugs seamlessly into our existing Grafana setup.</p>
<h3>What's Next?</h3>
<p>If you're looking to extend this Proof of Concept, consider adding these enhancements:</p>
<ul>
<li><p><strong>Automated Remediation Guardrails:</strong> Modify the Lambda function to trigger automated incident response runbooks when severe anomalies occur—such as automatically isolating or spinning down unmanaged development resources.</p>
</li>
<li><p><strong>Tag-Based FinOps Allocation:</strong> Update your Cost Explorer queries to cross-reference your organization's custom billing tags (e.g., <code>Owner</code>, <code>Environment</code>, <code>Project</code>). This would allow the AI engine to call out the specific team or project responsible for a spending spike.</p>
</li>
</ul>
<p><em>Have you experienced an unexpected cloud billing spike that went unnoticed for too long? How is your team currently tackling cloud cost tracking and observability? Let's discuss in the comments below!</em></p>
]]></content:encoded></item></channel></rss>