How to build a reserved instance and savings plan strategy that maximizes cloud discounts without over-committing — covering AWS, Azure, and GCP commitment models.
Cloud Reserved Instance Strategy: Commitment-Based Discounts That Actually Save Money
A cloud reserved instance strategy is a disciplined approach to committing to compute capacity over a fixed term—typically one or three years—in exchange for significant per-hour discounts. It matters when workloads are stable, predictable, and long-lived: batch processing jobs, backend services, data warehouses, and database clusters that run continuously. The strategy is not merely about buying reserved instances; it is about aligning commitment terms with workload patterns, avoiding overcommitment, and capturing savings through coordinated provisioning, scaling, and renewal.
The core of the strategy is defining the right commitment profile: instance type, region, duration, and scope. Each instance type has a distinct hourly rate, and the reservation provides a discount relative to the on-demand price. The decision must account for usage patterns, future load growth, and the cost of under- or over-provisioning.
To begin, extract historical usage data from the cloud provider’s metrics API. For example, in AWS, query the CloudWatch GetMetricStatistics endpoint with the InstanceType, CPUUtilization, and NetworkIn metrics over the past 180 days. Use curl to retrieve data:
curl -s \
-H "Authorization: Bearer $AWS_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"Namespace": "AWS/EC2",
"MetricDataQueries": [
{
"Id": "cpu",
"MetricStat": {
"Metric": {
"Namespace": "AWS/EC2",
"MetricName": "CPUUtilization",
"Dimensions": [
{ "Name": "InstanceId", "Value": "i-1234567890abcdef0" }
]
},
"Period": 3600,
"Stat": "Average"
}
}
],
"StartTime": "2024-01-01T00:00:00Z",
"EndTime": "2024-06-30T23:59:59Z"
}' \
https://monitoring.amazonaws.com
The response includes Datapoints with Timestamp, Average, and Unit. Aggregate these across all relevant instances to compute average CPU utilization per instance type.
From this, identify the instance types that are consistently utilized—say, c5.4xlarge at 60–80% CPU for 80% of the time—and determine the total expected instance-hours over the next 36 months.
Cloud providers offer multiple reservation types: standard, convertible, and scheduled. Each has distinct trade-offs in flexibility and discount.
c5.xlarge → c5.4xlarge) without losing the commitment.Use curl to create a standard reservation for c5.4xlarge in us-west-2 for 3 years:
curl -s \
-H "Authorization: Bearer $AWS_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"InstanceType": "c5.4xlarge",
"AvailabilityZone": "us-west-2a",
"Duration": 25920000, -- 3 years in seconds
"InstanceCount": 5,
"OfferingType": "Standard",
"PaymentOption": "AllUpfront"
}' \
https://ec2.amazonaws.com
The Duration field is in seconds: 365 × 24 × 3600 = 31,536,000, so 25,920,000 is 300 days (used for 3 years with 10% buffer). The PaymentOption determines upfront cost and discount structure:
AllUpfront: full payment at start, highest discount.PartialUpfront: partial upfront, remaining paid monthly.NoUpfront: no upfront payment, lower discount.The response includes ReservationId, Start, End, and Status. A reservation is Active when Start ≤ Now ≤ End.
Once reserved, the system must consume the instances efficiently. The reservation is a commitment, not a guarantee: if the instance is not running during the reservation period, the provider applies the discount only to actual running hours.
To ensure alignment, create a deployment script that starts instances from a shared AMI and tags them with ReservationId and CommitmentType.
#!/bin/bash
INSTANCE_TYPE=$1
RESERVATION_ID=$2
REGION=$3
aws ec2 run-instances \
--image-id ami-12345678 \
--instance-type $INSTANCE_TYPE \
--count 1 \
--region $REGION \
--tag-specifications \
'ResourceType=instance,Tags=[{Key=ReservationId,Value='$RESERVATION_ID'}, {Key=CommitmentType,Value=Standard}]' \
--security-groups sg-12345678 \
--subnet-id subnet-12345678 \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":100,"VolumeType":"gp3"}}]'
The tag-specifications field is a JSON array of ResourceType and Tags, each tag being a key-value pair. The tag ReservationId is used by billing systems to match instances to reservations.
A common failure: multiple reservations overlap, but not all instances are running. The reservation is paid, but the instance is idle. The system must track when instances are active and report usage to the cloud provider.
Use aws ec2 describe-instances to query the status:
aws ec2 describe-instances \
--filters "Name=tag:ReservationId,Values=i-1234567890abcdef0" \
--query 'Reservations[*].Instances[*].[InstanceId, State.Name, LaunchTime]' \
--output table
The output includes InstanceId, State.Name (e.g., running, stopped), and LaunchTime. The system must ensure that instances are running during the reservation period.
If an instance is stopped during its reservation window, the provider applies the reservation discount only for the hours it was running. To avoid underutilization, implement a daily check that compares the reservation start/end with instance uptime.
#!/bin/bash
# check-reservation-utilization.sh
RESERVATION_ID=$1
REGION=$2
# Get reservation details
RESERVATION=$(aws ec2 describe-reservations \
--reservation-ids $RESERVATION_ID \
--region $REGION \
--query 'Reservations[0].[Start, End, InstanceCount]' \
--output json)
# Parse Start and End
START=$(echo "$RESERVATION" | jq -r '.[0]')
END=$(echo "$RESERVATION" | jq -r '.[1]')
INSTANCE_COUNT=$(echo "$RESERVATION" | jq -r '.[2]')
# Query all instances in the reservation
INSTANCES=$(aws ec2 describe-instances \
--filters "Name=tag:ReservationId,Values=$RESERVATION_ID" \
--region $REGION \
--query 'Reservations[*].Instances[*].[InstanceId, State.Name, LaunchTime, TerminationTime]' \
--output json)
# Compute total running hours
TOTAL_RUNNING_HOURS=0
for instance in $(echo "$INSTANCES" | jq -c '.[]'); do
INSTANCE_ID=$(echo "$instance" | jq -r '.[0]')
STATE=$(echo "$instance" | jq -r '.[1]')
LAUNCH_TIME=$(echo "$instance" | jq -r '.[2]')
TERMINATION_TIME=$(echo "$instance" | jq -r '.[3]')
if [[ "$STATE" == "running" ]]; then
# Running from launch to end of reservation
RUN_START=$LAUNCH_TIME
RUN_END=$END
else
# Running from launch to termination
RUN_START=$LAUNCH_TIME
RUN_END=$TERMINATION_TIME
fi
# Calculate difference in seconds
RUN_DURATION=$(python3 -c "
import datetime
start = datetime.datetime.fromisoformat('$RUN_START')
end = datetime.datetime.fromisoformat('$RUN_END')
print((end - start).total_seconds() / 3600)
")
TOTAL_RUNNING_HOURS=$(echo "$TOTAL_RUNNING_HOURS + $RUN_DURATION" | bc -l)
done
# Expected hours: instance_count × (end - start) in hours
EXPECTED_HOURS=$(echo "$INSTANCE_COUNT × ($END - $START) / 3600" | bc -l)
# Compare
if (( $(echo "$TOTAL_RUNNING_HOURS < 0.8 * $EXPECTED_HOURS" | bc -l) )); then
echo "Reservation $RESERVATION_ID underutilized: $TOTAL_RUNNING_HOURS hours vs expected $EXPECTED_HOURS"
fi
This script uses jq to parse JSON output, bc for floating-point arithmetic, and python3 to calculate time differences from ISO 8601 timestamps.
When using convertible reservations, the system must track which instance types are being used and when they are switched.
A reservation can be converted by calling ModifyReservedInstances:
{
"ReservedInstancesIds": ["r-12345678"],
"TargetInstanceType": "c5.8xlarge"
}
The provider updates the reservation’s InstanceType and begins applying the discount for c5.8xlarge from the moment of the update.
The key failure mode: a conversion is applied, but the new instance type is not launched in time. The reservation is discounted for c5.8xlarge, but the instance is still c5.4xlarge. The discount is lost.
To catch this, implement a nightly job that compares the reservation’s InstanceType with the actual instance type of its instances:
#!/bin/bash
# validate-convertible-reservation.sh
RESERVATION_ID=$1
REGION=$2
RESERVATION=$(aws ec2 describe-reservations \
--reservation-ids $RESERVATION_ID \
--region $REGION \
--query 'Reservations[0].[InstanceType, InstanceCount, State]' \
--output json)
CURRENT_TYPE=$(echo "$RESERVATION" | jq -r '.[0]')
EXPECTED_COUNT=$(echo "$RESERVATION" | jq -r '.[1]')
CURRENT_STATE=$(echo "$RESERVATION" | jq -r '.[2]')
# Get all instances in reservation
INSTANCES=$(aws ec2 describe-instances \
--filters "Name=tag:ReservationId,Values=$RESERVATION_ID" \
--region $REGION \
--query 'Reservations[*].Instances[*].[InstanceId, InstanceType]' \
--output json)
# Check all instances have correct type
for instance in $(echo "$INSTANCES" | jq -c '.[]'); do
INSTANCE_ID=$(echo "$instance" | jq -r '.[0]')
INSTANCE_TYPE=$(echo "$instance" | jq -r '.[1]')
if [[ "$INSTANCE_TYPE" != "$CURRENT_TYPE" ]]; then
echo "Instance $INSTANCE_ID in reservation $RESERVATION_ID is $INSTANCE_TYPE, expected $CURRENT_TYPE"
fi
done
If the instance type differs from the reservation’s InstanceType, the system logs a warning and triggers a remediation process.
Reservations can be renewed manually or automatically. The renewal process must account for changes in workload, pricing, and commitment terms.
To renew a reservation, call PurchaseReservedInstancesOffering:
{
"ReservedInstancesOfferingId": "ro-12345678",
"InstanceCount": 3,
"PaymentOption": "AllUpfront",
"Tenancy": "default",
"Tags": [
{ "Key": "Renewed", "Value": "true" }
]
}
The ReservedInstancesOfferingId is the ID of a new offering, typically generated by DescribeReservedInstancesOfferings and selected based on pricing and availability.
The renewal is not immediate. The new reservation starts at Start time, but the old reservation continues until End. During this overlap, instances may run under two reservations.
To manage this, track the Active period for each reservation and calculate the total commitment cost per instance-hour.
A sharp edge: when an instance is stopped during the overlap period, the provider applies the reservation discount for the instance type in the reservation that was active at the time of the stop.
Thus, a single instance may be charged under multiple reservations simultaneously, but each reservation applies only to the hours the instance was running during that reservation’s active window.
The system must collect usage data per reservation and compute the effective discount per hour.
The following failures silently reduce savings:
Start time, but not counted under reservation.ReservationId missing or incorrect.ModifyReservedInstances call issued, but instance type not updated in the cloud provider.The system must validate each reservation’s Start and End dates against actual instance uptime and instance type history.
A final check: compare the total reservation cost against the total on-demand cost for the same workload. The savings are realized only when the reservation is fully utilized.
The strategy is not just buying reserved instances. It is about ensuring that the commitment is honored—every hour, every instance, every conversion. The money is saved, but only if the mechanism is executed.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy