Back
6_monitor_deploy
#!/usr/bin/env bash
# ================================================================
# CLOUD MONITORING — DEPLOY
# Monitor Resources Using AWS CloudWatch
# Creates: EC2 + SNS Email + 3 Alarms + Dashboard
#
# AMI : Latest Ubuntu Server LTS
# TYPE : t3.micro (Free Tier eligible)
# RUN : chmod +x 6_monitor_deploy.sh && ./6_monitor_deploy.sh
# DELETE : ./6_monitor_cleanup.sh
# STATE : $HOME/aws_state_monitor.txt
# ================================================================
set -euo pipefail
REGION="ap-south-1"
AMI_ID=$(aws ec2 describe-images --owners 099720109477 \
--filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
--region "$REGION" --query "sort_by(Images, &CreationDate)[-1].ImageId" --output text)
INSTANCE_TYPE="t3.micro"
KEY_NAME="Practical-KeyPair"
STATE_FILE="$HOME/aws_state_monitor.txt"
AVAILABILITY_ZONE=$(aws ec2 describe-availability-zones --region "$REGION" --query "AvailabilityZones[0].ZoneName" --output text)
# Resource Names
TOPIC_NAME="Practical-Monitoring-Alerts"
INST_NAME="Monitor-Target-Instance"
SG_NAME="Practical-Monitoring-SG"
DASHBOARD_NAME="Practical-Compute-Dashboard"
G='\033[0;32m'; B='\033[0;34m'; Y='\033[1;33m'; NC='\033[0m'
info() { echo -e "${B}[INFO]${NC} $*"; }
success() { echo -e "${G}[OK]${NC} $*"; }
banner() { echo -e "\n${Y}══════════════════════════════════════════${NC}"; \
echo -e "${Y} $*${NC}"; \
echo -e "${Y}══════════════════════════════════════════${NC}\n"; }
banner "CLOUD MONITORING — CloudWatch Setup"
echo ""
read -rp " Enter your email for CloudWatch alert notifications: " ALERT_EMAIL
echo ""
# ── Step 1: Key Pair ─────────────────────────────────────────
info "Step 1 — Key Pair: $KEY_NAME"
if aws ec2 describe-key-pairs --key-names "$KEY_NAME" --region "$REGION" >/dev/null 2>&1; then
info " Key Pair '$KEY_NAME' already exists. Using it."
else
info " Creating Key Pair: $KEY_NAME"
aws ec2 create-key-pair --key-name "$KEY_NAME" --region "$REGION" \
--query "KeyMaterial" --output text > "$HOME/${KEY_NAME}.pem"
chmod 400 "$HOME/${KEY_NAME}.pem"
success "Key Pair created → $HOME/${KEY_NAME}.pem"
fi
# ── Step 2: Networking (Independence & "From Scratch" logic) ───
info "Step 2 — Networking: VPC Discovery"
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=Practical-VPC" --query "Vpcs[0].VpcId" --output text --region "$REGION" 2>/dev/null || echo "None")
if [[ "$VPC_ID" == "None" || "$VPC_ID" == "null" ]]; then
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --region "$REGION" --query "Vpcs[0].VpcId" --output text 2>/dev/null || echo "None")
fi
if [[ "$VPC_ID" == "None" || "$VPC_ID" == "null" ]]; then
info " No suitable VPC found. Creating 'Practical-Monitor-VPC'..."
VPC_ID=$(aws ec2 create-vpc --cidr-block "10.60.0.0/16" --region "$REGION" --query "Vpc.VpcId" --output text)
aws ec2 create-tags --resources "$VPC_ID" --region "$REGION" --tags Key=Name,Value="Practical-Monitor-VPC"
aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-hostnames --region "$REGION"
aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-support --region "$REGION"
IGW_ID=$(aws ec2 create-internet-gateway --region "$REGION" --query "InternetGateway.InternetGatewayId" --output text)
aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" --region "$REGION"
# Route Table
RT_ID=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --region "$REGION" --query "RouteTable.RouteTableId" --output text)
aws ec2 create-route --route-table-id "$RT_ID" --destination-cidr-block "0.0.0.0/0" --gateway-id "$IGW_ID" --region "$REGION" > /dev/null
fi
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --region "$REGION" --query "Subnets[0].SubnetId" --output text 2>/dev/null || echo "None")
if [[ "$SUBNET_ID" == "None" || "$SUBNET_ID" == "null" ]]; then
info " Creating subnet..."
SUBNET_ID=$(aws ec2 create-subnet --vpc-id "$VPC_ID" --cidr-block "10.60.1.0/24" --availability-zone "$AVAILABILITY_ZONE" --region "$REGION" --query "Subnet.SubnetId" --output text 2>/dev/null || \
aws ec2 create-default-subnet --availability-zone "$AVAILABILITY_ZONE" --region "$REGION" --query "Subnet.SubnetId" --output text)
fi
success "VPC: $VPC_ID | Subnet: $SUBNET_ID"
# ── Step 2: Security Group ───────────────────────────────────
info "Step 2 — Security Group: $SG_NAME"
SG_ID=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=$SG_NAME" \
--region "$REGION" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null || echo "None")
if [[ "$SG_ID" == "None" || "$SG_ID" == "null" ]]; then
info " Creating Security Group..."
SG_ID=$(aws ec2 create-security-group \
--group-name "$SG_NAME" --description "CloudWatch Monitoring Demo" \
--vpc-id "$VPC_ID" --region "$REGION" --query "GroupId" --output text)
aws ec2 create-tags --resources "$SG_ID" --region "$REGION" \
--tags Key=Name,Value="$SG_NAME"
success "Security Group created: $SG_ID"
else
info " Security Group already exists ($SG_ID)."
fi
aws ec2 authorize-security-group-ingress \
--group-id "$SG_ID" --protocol tcp --port 22 --cidr "0.0.0.0/0" --region "$REGION" 2>/dev/null || true
# ── Step 3: EC2 Instance ──────────────────────────────────────
info "Step 3 — EC2 Instance: $INST_NAME"
INSTANCE_ID=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=$INST_NAME" "Name=instance-state-name,Values=running" \
--region "$REGION" --query "Reservations[0].Instances[0].InstanceId" --output text 2>/dev/null || echo "None")
if [[ "$INSTANCE_ID" == "None" || "$INSTANCE_ID" == "null" ]]; then
info " Launching EC2 Instance..."
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" \
--key-name "$KEY_NAME" --security-group-ids "$SG_ID" \
--subnet-id "$SUBNET_ID" \
--monitoring "Enabled=true" --region "$REGION" --count 1 \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$INST_NAME}]" \
--query "Instances[0].InstanceId" --output text)
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID" --region "$REGION"
success "Instance launched: $INSTANCE_ID"
else
info " Instance already exists ($INSTANCE_ID)."
fi
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids "$INSTANCE_ID" \
--region "$REGION" \
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
success "EC2: $INSTANCE_ID ($PUBLIC_IP) — detailed monitoring ON"
# ── Step 4: SNS Topic and Subscription ───────────────────────
info "Step 4 — SNS Topic: $TOPIC_NAME"
SNS_ARN=$(aws sns create-topic --name "$TOPIC_NAME" \
--region "$REGION" --query "TopicArn" --output text)
success "SNS Topic checked/created: $SNS_ARN"
info " SNS Subscription for: $ALERT_EMAIL"
aws sns subscribe --topic-arn "$SNS_ARN" \
--protocol email --notification-endpoint "$ALERT_EMAIL" \
--region "$REGION" > /dev/null 2>/dev/null || true
success "SNS Subscription checked/created."
echo ""
echo " ╔══════════════════════════════════════════════════╗"
echo " ║ ✉️ Check $ALERT_EMAIL "
echo " ║ Confirm the AWS SNS subscription NOW. ║"
echo " ╚══════════════════════════════════════════════════╝"
echo ""
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# ── Step 5: CloudWatch Alarms ─────────────────────────────────
info "Step 5 — Creating Alarm: CPU > 70%"
aws cloudwatch put-metric-alarm \
--alarm-name "HighCPU-Alarm" \
--alarm-description "CPU > 70% for 2 consecutive minutes" \
--metric-name "CPUUtilization" --namespace "AWS/EC2" --statistic "Average" \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--period 60 --evaluation-periods 2 --threshold 70 \
--comparison-operator "GreaterThanThreshold" \
--alarm-actions "$SNS_ARN" --ok-actions "$SNS_ARN" \
--treat-missing-data "notBreaching" --region "$REGION" 2>/dev/null || true
success "Alarm: HighCPU-Alarm (CPU > 70%)"
info "Step 6 — Creating Alarm: Status Check Failed"
aws cloudwatch put-metric-alarm \
--alarm-name "StatusCheck-Alarm" \
--alarm-description "EC2 status check failed" \
--metric-name "StatusCheckFailed" --namespace "AWS/EC2" --statistic "Maximum" \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--period 60 --evaluation-periods 2 --threshold 1 \
--comparison-operator "GreaterThanOrEqualToThreshold" \
--alarm-actions "$SNS_ARN" \
--treat-missing-data "notBreaching" --region "$REGION" 2>/dev/null || true
success "Alarm: StatusCheck-Alarm"
info "Step 7 — Creating Alarm: High Network In (>5MB)"
aws cloudwatch put-metric-alarm \
--alarm-name "HighNetwork-Alarm" \
--alarm-description "Network In > 5MB" \
--metric-name "NetworkIn" --namespace "AWS/EC2" --statistic "Average" \
--dimensions "Name=InstanceId,Value=$INSTANCE_ID" \
--period 300 --evaluation-periods 1 --threshold 5000000 \
--comparison-operator "GreaterThanThreshold" \
--alarm-actions "$SNS_ARN" \
--treat-missing-data "notBreaching" --region "$REGION" 2>/dev/null || true
success "Alarm: HighNetwork-Alarm"
# ── Step 8: CloudWatch Dashboard ─────────────────────────────
info "Step 8 — Creating CloudWatch Dashboard: $DASHBOARD_NAME"
DASH_BODY=$(cat <<EOF
{
"widgets": [
{
"type": "metric",
"x": 0, "y": 0, "width": 12, "height": 6,
"properties": {
"title": "CPU Utilization",
"region": "$REGION",
"metrics": [["AWS/EC2","CPUUtilization","InstanceId","$INSTANCE_ID"]],
"period": 60, "stat": "Average", "view": "timeSeries",
"annotations":{"horizontal":[{"label":"Threshold","value":70}]}
}
},
{
"type": "metric",
"x": 12, "y": 0, "width": 12, "height": 6,
"properties": {
"title": "Network In/Out",
"region": "$REGION",
"metrics": [
["AWS/EC2","NetworkIn","InstanceId","$INSTANCE_ID"],
["AWS/EC2","NetworkOut","InstanceId","$INSTANCE_ID"]
],
"period": 60, "stat": "Average", "view": "timeSeries"
}
},
{
"type": "alarm",
"x": 0, "y": 6, "width": 24, "height": 4,
"properties": {
"title": "Alarm Status",
"alarms": [
"arn:aws:cloudwatch:$REGION:$ACCOUNT_ID:alarm:HighCPU-Alarm",
"arn:aws:cloudwatch:$REGION:$ACCOUNT_ID:alarm:StatusCheck-Alarm",
"arn:aws:cloudwatch:$REGION:$ACCOUNT_ID:alarm:HighNetwork-Alarm"
]
}
}
]
}
EOF
)
aws cloudwatch put-dashboard \
--dashboard-name "$DASHBOARD_NAME" \
--dashboard-body "$DASH_BODY" \
--region "$REGION"
success "Dashboard: $DASHBOARD_NAME (3 widgets)"
info "Alarm Status:"
aws cloudwatch describe-alarms \
--alarm-names "HighCPU-Alarm" "StatusCheck-Alarm" "HighNetwork-Alarm" \
--region "$REGION" \
--query "MetricAlarms[].{Name:AlarmName,State:StateValue,Metric:MetricName,Threshold:Threshold}" \
--output table
cat > "$STATE_FILE" <<EOF
REGION=$REGION
INSTANCE_ID=$INSTANCE_ID
SG_ID=$SG_ID
VPC_ID=$VPC_ID
SNS_ARN=$SNS_ARN
KEY_NAME=$KEY_NAME
PUBLIC_IP=$PUBLIC_IP
ALERT_EMAIL=$ALERT_EMAIL
AMI_ID=$AMI_ID
ACCOUNT_ID=$ACCOUNT_ID
EOF
banner "CLOUD MONITORING — COMPLETE SUMMARY"
cat <<SUMMARY
╔══════════════════════════════════════════════════════════════╗
║ RESOURCES CREATED ║
╠═══════════════════════╦══════════════════════════════════════╣
║ Resource ║ Value ║
╠═══════════════════════╬══════════════════════════════════════╣
║ EC2 Instance ║ $INSTANCE_ID ║
║ EC2 Public IP ║ $PUBLIC_IP ║
║ Security Group ║ $SG_ID ║
║ Key Pair ║ $KEY_NAME ║
║ SNS Topic ║ $TOPIC_NAME ║
║ SNS Email ║ $ALERT_EMAIL ║
║ CW Alarm 1 ║ HighCPU-Alarm (CPU>70%, 2 periods) ║
║ CW Alarm 2 ║ StatusCheck-Alarm (EC2 failure) ║
║ CW Alarm 3 ║ HighNetwork-Alarm (>5MB) ║
║ CW Dashboard ║ $DASHBOARD_NAME ║
║ AMI Used ║ $AMI_ID ║
║ Region ║ $REGION ║
╠═══════════════════════╩══════════════════════════════════════╣
║ DETAILS NEEDED TO DELETE ║
╠══════════════════════════════════════════════════════════════╣
║ Instance ID : $INSTANCE_ID ║
║ SG ID : $SG_ID ║
║ SNS ARN : (stored in state file) ║
║ Alarm Names : HighCPU, StatusCheck, HighNetwork ║
║ Dashboard : $DASHBOARD_NAME ║
║ State File : $STATE_FILE ║
╠══════════════════════════════════════════════════════════════╣
║ Run: ./6_monitor_cleanup.sh (reads state file automatically) ║
╚══════════════════════════════════════════════════════════════╝
View Dashboard: CloudWatch → Dashboards → Compute-Monitoring-Dashboard
View Alarms: CloudWatch → Alarms → All Alarms
Stress test (trigger CPU alarm):
EC2 Console → $INSTANCE_ID → Connect → EC2 Instance Connect
sudo apt update && sudo apt install -y stress && stress --cpu 2 --timeout 300
SUMMARY