Using CloudFormation Mappings to define preset resource reservations for rightsizing
Using CloudFormation Mappings to define preset cpu, memory or instance types for rightsizing infrastructure
Table Of Contents
Today I Explained
When developing within AWS (& the cloud in-general), it is often the case that multiple environments of an applications exist. This can be for the purpose of prototyping, staging environments for ensuring new changes are working as intended, or taking advantage of computing savings that can exist within other AWS Regions.
This can look something like the following:
┌────────AWS────────┐
│ │
▼ ▼
Staging ┌──Production──┐
┌──────────┐ │ │
│x │ ▼ ▼
└──────────┘ us-east-1 eu-central-1
┌──────────┐ ┌──────────┐
│xxx │ │xxxxxxxx │
└──────────┘ └──────────┘
In the code block, it shows a graph with a top level node named “AWS”. This node has 2 child nodes, Staging & Production. The staging node has a box below it, showing a utilization of 10%. The production node has 2 child nodes,
us-east-1
andeu-central-1
. Theus-east-1
node has a box below it, showing a utilization of 30%, and theeu-central-1
node has a box below it, showing a utilization of 80%.
This can cause unintended wasteful spending within AWS, as not all environments of the application will have the same resource needs. A production environment requiring 16GB of memory, most likely isn’t needed for the staging environment which isn’t receiving the same traffic as the production environment. The same with other production environments in which the production traffic isn’t evenly distribution between them.
This is illustrated in the above code block, which shows how much of the allocated resources each of the environments is actually using (10% staging, 30% us-east-1, and 80% eu-central-1).
When working within CloudFormation, you could parameterize the instance type of an EC2 or the requested resources of a lambda or ECS task. This may present an organization challenge if many environments exist. To be consistent across services, it is easier to refer to the cpu/memory/etc requested by our application as the resource reservations.
An alternative approach is to define a set of presets within the CloudFormation template itself, using the Mapping
to declare the properties such as InstanceType
. A single parameter can be used within the CloudFormation template to select from each of these definitions.
AWSTemplateFormatVersion: "2010-09-09"
# ...
Parameters:
PresetType:
ConstraintDescription: must be the path to a node contained within the Mapping 'PresetTypeMapping'
Type: String
Default: standby
AllowedValues:
- standby
- default
- staging
Mappings:
PresetTypeMapping:
standby:
InstanceType: t1.micro
default:
InstanceType: t2.nano
staging:
InstanceType: t2.micro
Outputs:
InstanceType:
Value:
Fn::FindInMap:
- PresetTypeMapping
- Ref: PresetType
- InstanceType
This approach allows for all of our environments to select from the preset resource reservations an approach resoruce reservation based on the workflows.
A note on overrides
In the event of a hotfix, or should a case arise in which the need exists to override the preset resource reservation, this current pattern would require modifying the CloudFormation template to change the resource reservation for a given CloudFormation stack. This is less than ideal, and presents an opportunity for mistakes.
This can be addressed by adding support to the CloudFormation template for overrides. This is a pattern in CloudFormation in which a Parameter exists, that should it be set, will override the default configuration behaviour.
This can be accomplished like so:
# ... (Mapping & others)
Parameters:
# ...
InstanceType:
Description: Override the preset instance. If left blank, InstanceType will be determined by the presets.
Type: String
Default: ""
ConstraintDescription: must be a valid EC2 instance type, or blank
AllowedValues:
- ""
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
Conditions:
IsInstanceTypeDefault:
Fn::Equals:
- Ref: InstanceType
- ""
Outputs:
InstanceType:
Description: The 'InstanceType' from the presets, unless overwritten by the parameter 'InstanceType'
Value:
Fn::If:
# If unset, use the Preset, if set, use the parameter
- IsInstanceTypeDefault
- Fn::FindInMap:
- PresetTypeMapping
- Ref: PresetType
- InstanceType
- Ref: InstanceType
A note on rightsizing with presets
The usage of presets doesn’t mean that all environments will be perfectly rightsized for their compute workloads. Depending on the number of environments, and the nature of the workload, it can still mean suboptimal utilization in environments. What this pattern does is provide an approach to rightsize environments in a way that doesn’t require the adoption of a new deployment paradigm, or the overhead of managing per-environment parameters for the resource reservations.