AWS for DevOps — Project 5: Monitoring & Alerting System

8 minute read

Project Overview

Build a comprehensive monitoring and alerting system using CloudWatch, SNS, Lambda, and EventBridge. Set up dashboards, alarms, automated notifications, and self-healing automation that responds to infrastructure events.

Real-World Analogy: You’re building the security monitoring center for a building — cameras (metrics), alarm systems (CloudWatch Alarms), a phone tree (SNS notifications), and automated door locks (Lambda auto-remediation) that respond to intrusions without human intervention.

Detail Value
Difficulty ⭐⭐⭐ (Advanced)
Time 2-3 hours
Cost ~$0 (mostly within Free Tier)
Services CloudWatch, SNS, Lambda, EventBridge, EC2

Architecture

┌──────────────────────────────────────────────────────────────┐
│               Monitoring & Alerting System                    │
│                                                              │
│  ┌──────────┐   ┌──────────────┐   ┌─────────────────────┐ │
│  │CloudWatch│──→│   Alarms      │──→│       SNS           │ │
│  │ Metrics  │   │ (thresholds)  │   │  ┌──────────────┐  │ │
│  └──────────┘   └──────────────┘   │  │ Email Notify  │  │ │
│                                     │  │ Slack Webhook │  │ │
│  ┌──────────┐   ┌──────────────┐   │  │ Lambda Func   │  │ │
│  │CloudWatch│──→│ Log Metric   │   │  └──────────────┘  │ │
│  │  Logs    │   │   Filters    │   └─────────────────────┘ │
│  └──────────┘   └──────────────┘                            │
│                                                              │
│  ┌──────────────┐   ┌──────────────────────────────────┐   │
│  │ EventBridge  │──→│  Lambda Auto-Remediation          │   │
│  │  Rules       │   │  - Restart unhealthy instances    │   │
│  │              │   │  - Isolate compromised resources  │   │
│  │  Events:     │   │  - Scale up on high utilization   │   │
│  │  - EC2 state │   └──────────────────────────────────┘   │
│  │  - Health    │                                           │
│  │  - GuardDuty │   ┌──────────────────────────────────┐   │
│  └──────────────┘   │  CloudWatch Dashboard             │   │
│                      │  - CPU, Memory, Disk              │   │
│                      │  - Request count, Latency         │   │
│                      │  - Error rates, Alarm status      │   │
│                      └──────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────┘

Step 1 — Create SNS Topics for Notifications

# Create notification topics
$ aws sns create-topic --name critical-alerts \
    --tags Key=Project,Value=practice-5
{
    "TopicArn": "arn:aws:sns:us-east-1:123456789012:critical-alerts"
}

$ aws sns create-topic --name warning-alerts \
    --tags Key=Project,Value=practice-5
{
    "TopicArn": "arn:aws:sns:us-east-1:123456789012:warning-alerts"
}

$ aws sns create-topic --name info-alerts \
    --tags Key=Project,Value=practice-5

# Subscribe your email
$ aws sns subscribe \
    --topic-arn arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --protocol email \
    --notification-endpoint your-email@example.com

# Confirm the subscription (check your email and click confirm)

# Verify subscription
$ aws sns list-subscriptions-by-topic \
    --topic-arn arn:aws:sns:us-east-1:123456789012:critical-alerts

Notification Tiers

Tier Topic Use Case Response Time
Critical critical-alerts Service down, security breach Immediate
Warning warning-alerts High CPU, disk filling up Within 1 hour
Info info-alerts Deployments, scaling events Next business day

Step 2 — Create CloudWatch Alarms

CPU Utilization Alarm

# High CPU alarm (Warning at 70%, Critical at 90%)
$ aws cloudwatch put-metric-alarm \
    --alarm-name "EC2-HighCPU-Warning" \
    --alarm-description "EC2 CPU utilization above 70%" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --statistic Average \
    --period 300 \
    --threshold 70 \
    --comparison-operator GreaterThanThreshold \
    --evaluation-periods 3 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:warning-alerts \
    --ok-actions arn:aws:sns:us-east-1:123456789012:info-alerts \
    --dimensions Name=InstanceId,Value=i-0abc123 \
    --tags Key=Project,Value=practice-5

$ aws cloudwatch put-metric-alarm \
    --alarm-name "EC2-HighCPU-Critical" \
    --alarm-description "EC2 CPU utilization above 90%" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --statistic Average \
    --period 300 \
    --threshold 90 \
    --comparison-operator GreaterThanThreshold \
    --evaluation-periods 2 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --dimensions Name=InstanceId,Value=i-0abc123

ALB Alarm — High Error Rate

$ aws cloudwatch put-metric-alarm \
    --alarm-name "ALB-High5xxRate" \
    --alarm-description "ALB 5xx error rate above 5%" \
    --namespace AWS/ApplicationELB \
    --metric-name HTTPCode_Target_5XX_Count \
    --statistic Sum \
    --period 60 \
    --threshold 10 \
    --comparison-operator GreaterThanThreshold \
    --evaluation-periods 3 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --dimensions Name=LoadBalancer,Value=app/prod-alb/abc123

RDS Alarm — Low Free Storage

$ aws cloudwatch put-metric-alarm \
    --alarm-name "RDS-LowStorage" \
    --alarm-description "RDS free storage below 2GB" \
    --namespace AWS/RDS \
    --metric-name FreeStorageSpace \
    --statistic Average \
    --period 300 \
    --threshold 2000000000 \
    --comparison-operator LessThanThreshold \
    --evaluation-periods 1 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --dimensions Name=DBInstanceIdentifier,Value=prod-db

Billing Alarm

$ aws cloudwatch put-metric-alarm \
    --alarm-name "Billing-Exceeds-50USD" \
    --alarm-description "Estimated charges exceed $50" \
    --namespace AWS/Billing \
    --metric-name EstimatedCharges \
    --statistic Maximum \
    --period 21600 \
    --threshold 50 \
    --comparison-operator GreaterThanThreshold \
    --evaluation-periods 1 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --dimensions Name=Currency,Value=USD \
    --region us-east-1

Alarms Summary

Alarm Metric Threshold Severity
EC2-HighCPU-Warning CPUUtilization >70% (3 periods) Warning
EC2-HighCPU-Critical CPUUtilization >90% (2 periods) Critical
ALB-High5xxRate 5XX Count >10/min (3 periods) Critical
RDS-LowStorage FreeStorageSpace <2GB Critical
Billing-Exceeds-50USD EstimatedCharges >$50 Critical

Step 3 — Create Log Metric Filters

# Create a metric filter for application errors
$ aws logs put-metric-filter \
    --log-group-name /ecs/fargate-app \
    --filter-name "ErrorCount" \
    --filter-pattern "ERROR" \
    --metric-transformations \
        metricName=ApplicationErrors,metricNamespace=Custom/App,metricValue=1,defaultValue=0

# Create alarm on the custom metric
$ aws cloudwatch put-metric-alarm \
    --alarm-name "App-HighErrorRate" \
    --namespace Custom/App \
    --metric-name ApplicationErrors \
    --statistic Sum \
    --period 300 \
    --threshold 50 \
    --comparison-operator GreaterThanThreshold \
    --evaluation-periods 1 \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:warning-alerts

# Create metric filter for slow responses (>5 seconds)
$ aws logs put-metric-filter \
    --log-group-name /ecs/fargate-app \
    --filter-name "SlowResponses" \
    --filter-pattern '[timestamp, requestId, duration > 5000, ...]' \
    --metric-transformations \
        metricName=SlowRequests,metricNamespace=Custom/App,metricValue=1,defaultValue=0

Step 4 — Create Auto-Remediation Lambda

Lambda Function — Restart Unhealthy Instance

auto-remediate/index.py:

import json
import boto3
import os

ec2 = boto3.client('ec2')
sns = boto3.client('sns')

TOPIC_ARN = os.environ['SNS_TOPIC_ARN']

def handler(event, context):
    print(f"Received event: {json.dumps(event)}")

    # Extract instance ID from CloudWatch Alarm
    detail = event.get('detail', {})
    instance_id = None

    # Handle EC2 state change events
    if event.get('source') == 'aws.ec2':
        instance_id = detail.get('instance-id')
        state = detail.get('state')

        if state == 'stopped':
            print(f"Instance {instance_id} stopped. Restarting...")
            ec2.start_instances(InstanceIds=[instance_id])

            sns.publish(
                TopicArn=TOPIC_ARN,
                Subject=f"Auto-Remediation: Instance {instance_id} Restarted",
                Message=json.dumps({
                    'action': 'restart',
                    'instance_id': instance_id,
                    'reason': 'Instance unexpectedly stopped',
                    'status': 'restarted'
                }, indent=2)
            )

    # Handle CloudWatch Alarm events
    elif event.get('source') == 'aws.cloudwatch':
        alarm_name = detail.get('alarmName', '')
        state_value = detail.get('state', {}).get('value', '')

        if state_value == 'ALARM' and 'HighCPU' in alarm_name:
            # Could trigger scaling or instance replacement
            sns.publish(
                TopicArn=TOPIC_ARN,
                Subject=f"Alert: {alarm_name} in ALARM state",
                Message=json.dumps({
                    'alarm': alarm_name,
                    'state': state_value,
                    'reason': detail.get('state', {}).get('reason', ''),
                    'action': 'notification_sent'
                }, indent=2)
            )

    return {'statusCode': 200, 'body': 'Processed'}

Deploy the Lambda

# Create the Lambda role
$ aws iam create-role \
    --role-name auto-remediation-role \
    --assume-role-policy-document '{
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "lambda.amazonaws.com"},
        "Action": "sts:AssumeRole"
      }]
    }'

$ aws iam put-role-policy \
    --role-name auto-remediation-role \
    --policy-name remediation-policy \
    --policy-document '{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["ec2:StartInstances", "ec2:StopInstances", "ec2:DescribeInstances"],
          "Resource": "*"
        },
        {
          "Effect": "Allow",
          "Action": ["sns:Publish"],
          "Resource": "arn:aws:sns:us-east-1:123456789012:critical-alerts"
        },
        {
          "Effect": "Allow",
          "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
          "Resource": "*"
        }
      ]
    }'

# Package and deploy
$ cd auto-remediate && zip function.zip index.py && cd ..
$ aws lambda create-function \
    --function-name auto-remediation \
    --runtime python3.12 \
    --handler index.handler \
    --role arn:aws:iam::123456789012:role/auto-remediation-role \
    --zip-file fileb://auto-remediate/function.zip \
    --environment Variables="{SNS_TOPIC_ARN=arn:aws:sns:us-east-1:123456789012:critical-alerts}" \
    --timeout 30 \
    --tags Project=practice-5

Step 5 — Create EventBridge Rules

Rule 1: EC2 Instance State Changes

$ aws events put-rule \
    --name "ec2-state-change" \
    --description "Trigger on EC2 instance state changes" \
    --event-pattern '{
      "source": ["aws.ec2"],
      "detail-type": ["EC2 Instance State-change Notification"],
      "detail": {
        "state": ["stopped", "terminated"]
      }
    }' \
    --tags Key=Project,Value=practice-5

# Add Lambda as target
$ aws events put-targets \
    --rule ec2-state-change \
    --targets "Id=auto-remediation,Arn=arn:aws:lambda:us-east-1:123456789012:function:auto-remediation"

# Grant EventBridge permission to invoke Lambda
$ aws lambda add-permission \
    --function-name auto-remediation \
    --statement-id eventbridge-ec2 \
    --action lambda:InvokeFunction \
    --principal events.amazonaws.com \
    --source-arn arn:aws:events:us-east-1:123456789012:rule/ec2-state-change

Rule 2: CloudWatch Alarm State Changes

$ aws events put-rule \
    --name "alarm-state-change" \
    --description "Trigger on CloudWatch alarm state changes" \
    --event-pattern '{
      "source": ["aws.cloudwatch"],
      "detail-type": ["CloudWatch Alarm State Change"],
      "detail": {
        "state": {
          "value": ["ALARM"]
        }
      }
    }'

$ aws events put-targets \
    --rule alarm-state-change \
    --targets "Id=auto-remediation,Arn=arn:aws:lambda:us-east-1:123456789012:function:auto-remediation"

$ aws lambda add-permission \
    --function-name auto-remediation \
    --statement-id eventbridge-alarm \
    --action lambda:InvokeFunction \
    --principal events.amazonaws.com \
    --source-arn arn:aws:events:us-east-1:123456789012:rule/alarm-state-change

Rule 3: Scheduled Health Check (every 5 minutes)

$ aws events put-rule \
    --name "scheduled-health-check" \
    --description "Run health check every 5 minutes" \
    --schedule-expression "rate(5 minutes)"

$ aws events put-targets \
    --rule scheduled-health-check \
    --targets "Id=health-check,Arn=arn:aws:lambda:us-east-1:123456789012:function:auto-remediation"

Step 6 — Create CloudWatch Dashboard

$ aws cloudwatch put-dashboard \
    --dashboard-name "Production-Overview" \
    --dashboard-body '{
      "widgets": [
        {
          "type": "metric",
          "x": 0, "y": 0, "width": 12, "height": 6,
          "properties": {
            "title": "EC2 CPU Utilization",
            "metrics": [
              ["AWS/EC2", "CPUUtilization", "InstanceId", "i-0abc123", {"stat": "Average"}]
            ],
            "period": 300,
            "view": "timeSeries"
          }
        },
        {
          "type": "metric",
          "x": 12, "y": 0, "width": 12, "height": 6,
          "properties": {
            "title": "ALB Request Count",
            "metrics": [
              ["AWS/ApplicationELB", "RequestCount", "LoadBalancer", "app/prod-alb/abc123", {"stat": "Sum"}]
            ],
            "period": 60,
            "view": "timeSeries"
          }
        },
        {
          "type": "metric",
          "x": 0, "y": 6, "width": 12, "height": 6,
          "properties": {
            "title": "RDS Connections",
            "metrics": [
              ["AWS/RDS", "DatabaseConnections", "DBInstanceIdentifier", "prod-db", {"stat": "Average"}]
            ],
            "period": 300,
            "view": "timeSeries"
          }
        },
        {
          "type": "metric",
          "x": 12, "y": 6, "width": 12, "height": 6,
          "properties": {
            "title": "Application Errors",
            "metrics": [
              ["Custom/App", "ApplicationErrors", {"stat": "Sum"}]
            ],
            "period": 300,
            "view": "timeSeries"
          }
        },
        {
          "type": "alarm",
          "x": 0, "y": 12, "width": 24, "height": 3,
          "properties": {
            "title": "Alarm Status",
            "alarms": [
              "arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2-HighCPU-Warning",
              "arn:aws:cloudwatch:us-east-1:123456789012:alarm:EC2-HighCPU-Critical",
              "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ALB-High5xxRate",
              "arn:aws:cloudwatch:us-east-1:123456789012:alarm:RDS-LowStorage"
            ]
          }
        }
      ]
    }'

Dashboard URL

https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#dashboards:name=Production-Overview

Step 7 — Test the System

# Test 1: Trigger a CPU alarm (stress test)
$ aws ssm send-command \
    --document-name "AWS-RunShellScript" \
    --targets "Key=instanceids,Values=i-0abc123" \
    --parameters 'commands=["stress --cpu 4 --timeout 600"]'

# Monitor the alarm
$ watch -n 30 'aws cloudwatch describe-alarms \
    --alarm-names EC2-HighCPU-Warning \
    --query "MetricAlarms[0].StateValue"'

# Test 2: Stop an instance to trigger auto-remediation
$ aws ec2 stop-instances --instance-ids i-0abc123

# Check Lambda logs
$ aws logs tail /aws/lambda/auto-remediation --follow

# Test 3: Verify email notification arrived
# Check your inbox for SNS notification

Step 8 — Clean Up

# Delete EventBridge rules and targets
$ aws events remove-targets --rule ec2-state-change --ids auto-remediation
$ aws events delete-rule --name ec2-state-change
$ aws events remove-targets --rule alarm-state-change --ids auto-remediation
$ aws events delete-rule --name alarm-state-change
$ aws events remove-targets --rule scheduled-health-check --ids health-check
$ aws events delete-rule --name scheduled-health-check

# Delete Lambda function
$ aws lambda delete-function --function-name auto-remediation

# Delete CloudWatch alarms
$ aws cloudwatch delete-alarms --alarm-names \
    EC2-HighCPU-Warning EC2-HighCPU-Critical \
    ALB-High5xxRate RDS-LowStorage \
    Billing-Exceeds-50USD App-HighErrorRate

# Delete dashboard
$ aws cloudwatch delete-dashboards --dashboard-names Production-Overview

# Delete SNS topics
$ aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789012:critical-alerts
$ aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789012:warning-alerts
$ aws sns delete-topic --topic-arn arn:aws:sns:us-east-1:123456789012:info-alerts

# Delete IAM role
$ aws iam delete-role-policy --role-name auto-remediation-role --policy-name remediation-policy
$ aws iam delete-role --role-name auto-remediation-role

What You Built

Component Purpose
3 SNS Topics Tiered notifications (Critical/Warning/Info)
6 CloudWatch Alarms CPU, errors, storage, billing thresholds
2 Log Metric Filters Application error and latency tracking
1 Lambda Function Auto-remediation (restart, notify)
3 EventBridge Rules Event-driven automation triggers
1 CloudWatch Dashboard Unified monitoring view

Event Flow Summary

EC2 stops unexpectedly
     │
     ↓
EventBridge (ec2-state-change rule)
     │
     ↓
Lambda (auto-remediation)
     │
     ├── Restart the instance
     │
     └── Publish to SNS (critical-alerts)
              │
              ├── Email notification
              └── (Could add Slack, PagerDuty, etc.)

Common Mistakes

Mistake Impact Fix
Missing Lambda permissions EventBridge can’t invoke Lambda Add lambda:InvokeFunction resource policy
Wrong event pattern Rules never trigger Test with aws events test-event-pattern
Too sensitive thresholds Alert fatigue Tune evaluation periods and thresholds
No OK actions Never know when issue resolves Add --ok-actions to alarms
Single notification channel Missed alerts Use multiple: email + Slack + PagerDuty

Next → Practice Exercises

Updated: