Manage CloudWatch Metrics and Alarms with the AWS CLI

Cloud servers send CPU metrics to a monitoring dashboard with an active alarm indicator

What You’ll Learn

In this lesson, you will use the AWS CLI to query EC2 CPU utilization and create an Amazon CloudWatch alarm for sustained high CPU usage. You will also learn how to inspect, update, disable, and remove alarms from the command line.

  • Query the AWS/EC2 CPUUtilization metric.
  • Understand metric dimensions, periods, statistics, and thresholds.
  • Create and inspect a CPU utilization alarm.
  • Manage an existing alarm with CloudWatch commands.

The Concept

CloudWatch metrics are time-series measurements collected from AWS resources. For an EC2 instance, the CPUUtilization metric reports the percentage of CPU capacity being used.

The AWS CLI provides several CloudWatch commands for working with these metrics:

  • get-metric-statistics retrieves statistics for one metric over a time range.
  • get-metric-data retrieves one or more metrics using metric data queries.
  • put-metric-alarm creates or updates an alarm.
  • describe-alarms displays alarm configuration and current state.
  • disable-alarm-actions and enable-alarm-actions control notification or remediation actions.
  • delete-alarms removes alarms.

A metric is identified by its namespace, metric name, and dimensions. In this lesson, the namespace is AWS/EC2, the metric name is CPUUtilization, and the dimension identifies a particular instance:

Namespace: AWS/EC2
Metric: CPUUtilization
Dimension: InstanceId=i-0123456789abcdef0

CloudWatch alarms evaluate metric data over one or more periods. For example, an alarm with a five-minute period and two evaluation periods can enter the ALARM state when CPU usage is above its threshold for two consecutive five-minute periods.

Basic Example

The following Bash session queries the average CPU utilization of one EC2 instance for the last 15 minutes and then creates an alarm when the average exceeds 70 percent for two consecutive five-minute periods.

Replace the instance ID and region with values from your AWS account. The commands use the default AWS CLI profile.

#!/usr/bin/env bash

set -euo pipefail

INSTANCE_ID="i-0123456789abcdef0"
REGION="us-east-1"
ALARM_NAME="production-web-high-cpu"

START_TIME=$(date -u -d "15 minutes ago" +"%Y-%m-%dT%H:%M:%SZ")
END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

aws cloudwatch get-metric-statistics \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
    --start-time "$START_TIME" \
    --end-time "$END_TIME" \
    --period 300 \
    --statistics Average \
    --region "$REGION" \
    --query 'sort_by(Datapoints, &Timestamp)[].{Timestamp:Timestamp,Average:Average}' \
    --output json

aws cloudwatch put-metric-alarm \
    --alarm-name "$ALARM_NAME" \
    --alarm-description "Alarm when EC2 CPU stays above 70 percent" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
    --statistic Average \
    --period 300 \
    --evaluation-periods 2 \
    --datapoints-to-alarm 2 \
    --threshold 70 \
    --comparison-operator GreaterThanThreshold \
    --treat-missing-data notBreaching \
    --region "$REGION"

aws cloudwatch describe-alarms \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION" \
    --query 'MetricAlarms[].{Name:AlarmName,State:StateValue,Reason:StateReason,Threshold:Threshold,Periods:EvaluationPeriods}' \
    --output table

The date commands create UTC timestamps. On macOS, the BSD date command does not support -d; use an equivalent timestamp-generation command for your operating system.

Expected Output

The metric query returns the available five-minute datapoints. The exact timestamps and values depend on the instance’s recent activity.

[
    {
        "Timestamp": "2025-03-08T14:40:00+00:00",
        "Average": 24.781
    },
    {
        "Timestamp": "2025-03-08T14:45:00+00:00",
        "Average": 31.204
    },
    {
        "Timestamp": "2025-03-08T14:50:00+00:00",
        "Average": 28.917
    }
]

The alarm inspection command displays the alarm name, current state, threshold, and number of evaluation periods. A newly created alarm may initially be in the INSUFFICIENT_DATA state while CloudWatch waits for enough datapoints.

How the Code Works

A top-to-bottom workflow starts by identifying an EC2 CPU metric with its instance dimension, queries five-minute datapoints, configures CloudWatch alarm thresholds and evaluation requirements, evaluates the data including missing-data handling, and then reaches an alarm state with optional actions. The alarm can also be inspected or managed operationally.
Query the correctly dimensioned EC2 CPU metric, configure sustained-breach rules, and manage the resulting CloudWatch alarm and its actions.

get-metric-statistics requires a time range and a period. Here, --period 300 requests five-minute intervals, while --statistics Average asks CloudWatch to calculate the average CPU value for each interval.

The --dimensions argument is important. EC2 publishes CPU utilization separately for each instance, so omitting Name=InstanceId,Value=... will not identify the intended instance.

The JMESPath expression passed to --query sorts datapoints by timestamp and keeps only the timestamp and average value. This makes the result easier to read than the full response, which can also contain other statistic fields.

put-metric-alarm creates the alarm if it does not exist. If an alarm with the same name already exists, the command updates its configuration. In this example:

  • --threshold 70 sets the CPU percentage limit.
  • --comparison-operator GreaterThanThreshold triggers when the value is above 70.
  • --evaluation-periods 2 evaluates two consecutive five-minute periods.
  • --datapoints-to-alarm 2 requires both periods to breach the threshold.
  • --treat-missing-data notBreaching prevents missing data from triggering the alarm.

This alarm has no --alarm-actions value, so it changes state but does not send an SNS notification or start an automated recovery action. In production, you would typically add an SNS topic ARN or another appropriate action after deciding how the alert should be handled.

Another Example

Alarm configuration often changes as an application moves between environments. The following commands inspect a CPU alarm, temporarily disable its actions during planned maintenance, update its threshold to 85 percent, and then re-enable the actions.

#!/usr/bin/env bash

set -euo pipefail

REGION="us-east-1"
ALARM_NAME="production-web-high-cpu"

printf '%s\n' 'Current alarm details:'
aws cloudwatch describe-alarms \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION" \
    --query 'MetricAlarms[].{Name:AlarmName,State:StateValue,Updated:AlarmConfigurationUpdatedTimestamp,Actions:AlarmActions}' \
    --output json

printf '\n%s\n' 'Disabling alarm actions for maintenance:'
aws cloudwatch disable-alarm-actions \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION"

printf '\n%s\n' 'Raising the threshold to 85 percent:'
aws cloudwatch put-metric-alarm \
    --alarm-name "$ALARM_NAME" \
    --alarm-description "Alarm when EC2 CPU stays above 85 percent" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value=i-0123456789abcdef0 \
    --statistic Average \
    --period 300 \
    --evaluation-periods 3 \
    --datapoints-to-alarm 2 \
    --threshold 85 \
    --comparison-operator GreaterThanThreshold \
    --treat-missing-data notBreaching \
    --region "$REGION"

printf '\n%s\n' 'Re-enabling alarm actions:'
aws cloudwatch enable-alarm-actions \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION"

aws cloudwatch describe-alarms \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION" \
    --query 'MetricAlarms[].{Name:AlarmName,State:StateValue,Threshold:Threshold,EvaluationPeriods:EvaluationPeriods,DatapointsToAlarm:DatapointsToAlarm}' \
    --output table

This is different from simply creating an alarm: it demonstrates operational management. The updated policy allows up to three five-minute periods to be evaluated, and at least two of those periods must exceed 85 percent before the alarm enters the ALARM state.

Common Mistakes

  • Using the wrong region: CloudWatch metrics and alarms are regional. Always pass the region containing the EC2 instance, or configure the correct default region.
  • Omitting the instance dimension: EC2 CPU metrics are associated with an InstanceId. A missing or incorrect dimension can produce no datapoints.
  • Confusing period and evaluation periods: A 300-second period is five minutes. Two evaluation periods means CloudWatch considers two five-minute periods; it does not mean two minutes.
  • Expecting an alarm to notify someone automatically: An alarm without --alarm-actions only changes state. Add an SNS topic ARN or another supported action when notifications are required.
  • Assuming a new alarm immediately has a definitive state: CloudWatch may report INSUFFICIENT_DATA until enough metric data has arrived.
  • Deleting an alarm during troubleshooting: If you only need to silence notifications, use disable-alarm-actions instead of delete-alarms.

Try It Yourself

Choose a running EC2 instance and use get-metric-statistics to retrieve its maximum CPU utilization for the last hour. Use 300-second periods and sort the returned datapoints by timestamp. Then inspect the result and decide whether an average-based or maximum-based alarm better fits your workload.

Challenge

Create a CloudWatch alarm for a different EC2 instance with these requirements:

  • Use the AWS/EC2 CPUUtilization metric.
  • Use the instance’s InstanceId dimension.
  • Set the statistic to Maximum.
  • Use five-minute periods.
  • Require two out of three periods to exceed 80 percent.
  • Treat missing data as not breaching.
  • Query the alarm afterward and display its name, state, threshold, and datapoint requirements.

Solution

#!/usr/bin/env bash

set -euo pipefail

INSTANCE_ID="i-0fedcba9876543210"
REGION="us-west-2"
ALARM_NAME="api-server-maximum-cpu"

aws cloudwatch put-metric-alarm \
    --alarm-name "$ALARM_NAME" \
    --alarm-description "Alarm when API server maximum CPU exceeds 80 percent" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value="$INSTANCE_ID" \
    --statistic Maximum \
    --period 300 \
    --evaluation-periods 3 \
    --datapoints-to-alarm 2 \
    --threshold 80 \
    --comparison-operator GreaterThanThreshold \
    --treat-missing-data notBreaching \
    --region "$REGION"

aws cloudwatch describe-alarms \
    --alarm-names "$ALARM_NAME" \
    --region "$REGION" \
    --query 'MetricAlarms[].{Name:AlarmName,State:StateValue,Threshold:Threshold,Statistic:Statistic,Period:Period,EvaluationPeriods:EvaluationPeriods,DatapointsToAlarm:DatapointsToAlarm}' \
    --output table

The solution uses Maximum rather than Average, so a short CPU spike can count as a breach for a five-minute period. With three evaluation periods and --datapoints-to-alarm 2, at least two of the three periods must exceed 80 percent.

Key Takeaways

  • Use get-metric-statistics to query EC2 CPU utilization over a specific time range.
  • CloudWatch metric dimensions, especially InstanceId, determine which resource is being measured.
  • put-metric-alarm creates a new alarm or updates an existing alarm with the same name.
  • Periods, evaluation periods, and datapoints-to-alarm control how sustained a condition must be before triggering.
  • Inspect alarms with describe-alarms, and disable actions when you need to silence notifications without deleting the alarm.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top