Create Cloud Watch Alarm using Terraform


Last 10 days of learning was amazing and today we will use most of it because we today we are going to create alarm using terraform.
So, Hustlers recall your learning and lets proceed further.
The Nautilus DevOps team is setting up monitoring in their AWS account. As part of this, they need to create a CloudWatch alarm.
Task Details:
Create a CloudWatch alarm named nautilus-alarm.
The alarm should monitor CPU utilization of an EC2 instance.
Trigger the alarm when CPU utilization exceeds 80%.
Set the evaluation period to 5 minutes.
Use a single evaluation period.
We are going to create a Terraform configuration to set up a CloudWatch alarm for an EC2 instance's CPU utilization.
And this EC2 instance will be in VPC so we need to create other component of VPC like - Subnet, Internet Gateway, Route Table, Security Group, and also we have to create the Route Table Association and then we will create CloudWatch Alarm and will take the information from above requirement and make the configuration file.
We assume that we are in the region us-east-1.
If not, change the provider accordingly. We'll create a security group allowing SSH (for example) and use an Amazon Linux 2 AMI.
Steps in main.tf:
Configure AWS provider.
Create a VPC (if we don't want to use default, but for simplicity, we can use default VPC). However, let's create a VPC to be more complete? But the requirement is to set up the alarm. Since the task doesn't specify, we can use the default VPC. But note: the default VPC might not exist in all regions. Alternatively, we can create a VPC, but that might be too much. Let me stick to using the default VPC for simplicity.
Create a security group for the EC2 instance allowing SSH (optional, if you want to access) and outbound traffic.
Launch an EC2 instance.
Create the CloudWatch alarm for that instance.
Since our task is about the alarm, and the instance is just a prerequisite, I will use the default VPC and subnets. But note: in production, we might not want to use default. However, for simplicity, we can use data sources to get the default VPC and subnets.
Let's do:
-Use data source to get the default VPC.
- Create a security group in that VPC.
- Launch an instance in the default VPC's subnet (we can use data source for the subnet as well). Alternatively, we can let Terraform use the default VPC without specifying, but then we have to set the subnet_id. However, to be explicit and avoid errors, we can use data sources.
But note: we don't need a public IP necessarily for monitoring, but if we want to access via SSH, we might want it.
Let's create:
- security group that allows SSH from anywhere (for simplicity in testing, but in production restrict it)
- EC2 instance with the security group and a key pair (if we want to SSH). But we don't have a key, so we can generate one.
However, the task doesn't require SSH access. We can skip the key and just create the instance without a key? But then we can't SSH. But the alarm doesn't require SSH.
So Let’s just not waste much of our time and create the main.tf file. I am keeping this bit simple but we can change the configuration according to our need.
# /home/bob/terraform/providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1" # Update region if needed
}
# VPC
resource "aws_vpc" "nautilus_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "NautilusVPC"
}
}
# Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.nautilus_vpc.id
tags = {
Name = "NautilusIGW"
}
}
# Subnet
resource "aws_subnet" "nautilus_subnet" {
vpc_id = aws_vpc.nautilus_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a" # Update AZ if needed
tags = {
Name = "NautilusSubnet"
}
}
# Route Table
resource "aws_route_table" "nautilus_rt" {
vpc_id = aws_vpc.nautilus_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "NautilusRouteTable"
}
}
# Route Table Association
resource "aws_route_table_association" "a" {
subnet_id = aws_subnet.nautilus_subnet.id
route_table_id = aws_route_table.nautilus_rt.id
}
# Security Group
resource "aws_security_group" "nautilus_sg" {
name = "nautilus_sg"
description = "Allow SSH and outbound traffic"
vpc_id = aws_vpc.nautilus_vpc.id
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "NautilusSG"
}
}
# EC2 Instance
resource "aws_instance" "nautilus_instance" {
ami = "ami-0c7217cdde317cfec" # Amazon Linux 2023 (us-east-1)
instance_type = "t3.micro"
subnet_id = aws_subnet.nautilus_subnet.id
vpc_security_group_ids = [aws_security_group.nautilus_sg.id]
tags = {
Name = "NautilusInstance"
}
}
# CloudWatch Alarm
resource "aws_cloudwatch_metric_alarm" "nautilus_alarm" {
alarm_name = "nautilus-alarm"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300 # 5 minutes
statistic = "Average"
threshold = 80
alarm_description = "Alarm when CPU exceeds 80%"
dimensions = {
InstanceId = aws_instance.nautilus_instance.id
}
}
Once again let understand the working of resources.
Explanation of Resources:
VPC: Creates an isolated network environment
Internet Gateway: Allows internet access for resources in the VPC
Subnet: Defines an IP address range within the VPC
Route Table & Association: Routes traffic between subnet and internet gateway
Security Group:
Allows SSH access (port 22) from any IP
Allows all outbound traffic
EC2 Instance:
Uses Amazon Linux 2023 AMI
t3.micro instance type (free tier eligible)
Deploys in the created subnet with security group
CloudWatch Alarm:
Monitors CPU utilization
Triggers when >80% for 5 minutes
Uses a single evaluation period
Deployment Steps:
# Navigate to the Terraform directory
cd /home/bob/terraform
# Initialize Terraform
terraform init
# Preview resources
terraform plan
# Create resources
terraform apply -auto-approve
Verification:
AWS Console → EC2:
- Verify "NautilusInstance" is running
AWS Console → CloudWatch → Alarms:
Verify "nautilus-alarm" exists
Check configuration: 80% threshold, 5-minute period
Test Alarm (optional):
SSH into instance (use generated key pair)
Install stress tool: sudo amazon-linux-extras install epel -y && sudo yum install stress -y
Generate CPU load: stress --cpu 1 --timeout 300
Verify alarm triggers in CloudWatch after 5 minutes
And Now comes the most important part of our practicing.
Cleanup (when complete):
terraform destroy -auto-approve
This configuration creates a complete, isolated environment with:
Networking infrastructure (VPC, subnet, routing)
Security controls
Compute instance
Monitoring solution
Hope this blog was for the so far learning in coming blog we will deep dive into some other topic and yes small step make big impact on learning we we won’t rush in learning and if you have any suggestion for me please leave your comment or also you can reach out to me my social media.
Subscribe to my newsletter
Read articles from Kunal Kumar Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kunal Kumar Singh
Kunal Kumar Singh
I am a DevOps Engineer working in MNC. Where I automate Infrastructure using various DevOps tools and AWS Cloud.