Managing EC2 instance sizes manually can be time-consuming, especially when scaling resources across multiple instances. In this guide, we’ll deploy an AWS Systems Manager (SSM) document using AWS CloudFormation (CFN) to automate the resizing of EC2 instances in bulk. This setup allows users to resize instances across different Availability Zones with a single click.
Overview
Our goal is to create a CloudFormation stack that:
- Deploys multiple EC2 instances in different Availability Zones
- Creates an SSM document to automate the resizing of EC2 instances
- Allows resizing based on Availability Zone, Tags, and Instance Type
- Enables automation through AWS Systems Manager
Instead of manually stopping, resizing, and restarting instances, this solution streamlines the entire process into an automated workflow.
Architecture
The architecture consists of:
- EC2 Instances - Launched with CloudFormation, spread across different Availability Zones
- AWS SSM Document - Defines the automation process for resizing instances
- IAM Role for SSM - Grants permissions to modify EC2 instance attributes
Step-by-Step Guide
1. Create a CloudFormation Template for EC2 Instances
We first create an EC2 CloudFormation (CFN) template (ec2.yaml) to launch instances in different Availability Zones.
ec2.yaml
AWSTemplateFormatVersion: 2010-09-09
#Description: EC2 with user data from CloudFormation.
Parameters:
InstanceType:
Description: WebServer EC2 instance type.
Type: String
Default: t2.micro
AllowedValues:
- t1.micro
- t2.nano
- t2.micro
- t2.small
- t2.medium
- t2.large
- m1.small
- m1.medium
- m1.large
- m1.xlarge
- m2.xlarge
- m2.2xlarge
- m2.4xlarge
- m3.medium
- m3.large
- m3.xlarge
- m3.2xlarge
- m4.large
- m4.xlarge
- m4.2xlarge
- m4.4xlarge
- m4.10xlarge
- c1.medium
- c1.xlarge
- c3.large
- c3.xlarge
- c3.2xlarge
- c3.4xlarge
- c3.8xlarge
- c4.large
- c4.xlarge
- c4.2xlarge
- c4.4xlarge
- c4.8xlarge
- g2.2xlarge
- g2.8xlarge
- r3.large
- r3.xlarge
- r3.2xlarge
- r3.4xlarge
- r3.8xlarge
- i2.xlarge
- i2.2xlarge
- i2.4xlarge
- i2.8xlarge
- d2.xlarge
- d2.2xlarge
- d2.4xlarge
- d2.8xlarge
- hi1.4xlarge
- hs1.8xlarge
- cr1.8xlarge
- cc2.8xlarge
- cg1.4xlarge
# Ec2Name:
# Description: Ec2 Resource name.
# Type: String
# BucketName:
# Description: BucketName.
# Default: ahmedsalem-testbuckettt
# Type: String
# ObjectPrefix:
# Description: idex.html prefix,for ex /
# Type: String
# Default: /
# KeyName:
# Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
# Type: 'AWS::EC2::KeyPair::KeyName'
# ConstraintDescription: must be the name of an existing EC2 KeyPair.
# SubnetId:
# Description: Subnet ID which will run the web server instanc into.
# Type: 'AWS::EC2::Subnet::Id'
LatestAmiId:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
SSHLocation:
Description: The IP address range that can be used to SSH to the EC2 instance.
Type: String
MinLength: '9'
MaxLength: '18'
Default: 0.0.0.0/0
AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
ConstraintDescription: Must be a valid IP CIDR range of the form x.x.x.x/x
Resources:
WebServerInstance1:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-ed2f8486
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_1"
- Key: "ssm-tag"
Value: "test-ssm"
# SecurityGroupIds:
# - !GetAtt "WebServerSecurityGroup.GroupId"
#IamInstanceProfile: !Ref IAMInstanceProfile
WebServerInstance2:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-0556b778
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_2"
- Key: "ssm-tag"
Value: "test-ssm"
# SecurityGroupIds:
# - !GetAtt "WebServerSecurityGroup.GroupId"
#IamInstanceProfile: !Ref IAMInstanceProfile
WebServerInstance3:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-34531b78
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_3"
# - Key: "ssm-tag"
# Value: "test-ssm"
# SecurityGroupIds:
# - !GetAtt "WebServerSecurityGroup.GroupId"
#IamInstanceProfile: !Ref IAMInstanceProfile
WebServerInstance4:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-ed2f8486
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_4"
WebServerInstance5:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-ed2f8486
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_5"
WebServerInstance6:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-ed2f8486
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_6"
WebServerInstance7:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
NetworkInterfaces:
- AssociatePublicIpAddress: "true"
DeviceIndex: "0"
SubnetId: subnet-ed2f8486
GroupSet:
- !GetAtt "WebServerSecurityGroup.GroupId"
Tags:
- Key: "Name"
Value: "server_7"
WebServerSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupDescription: Enable HTTP access via port 80 and SSH Access port 22.
# VpcId: vpc-0078f41e6b5568b6e
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: !Ref SSHLocation
# IAMInstanceProfile:
# Type: AWS::IAM::InstanceProfile
# Properties:
# Roles:
# - !Ref IAMRole
# InstanceProfileName: ec2-access-s3
# IAMRole:
# Type: AWS::IAM::Role
# Properties:
# RoleName: ec2-s3-access
# AssumeRolePolicyDocument:
# Version: '2012-10-17'
# Statement:
# - Effect: Allow
# Principal:
# Service: ec2.amazonaws.com
# Action: sts:AssumeRole
# Path: '/'
# Policies:
# - PolicyName: 'EC2Access'
# PolicyDocument:
# Version: '2012-10-17'
# Statement:
# - Effect: 'Allow'
# Action:
# - 's3:GetObject'
# Resource: !Sub 'arn:aws:s3:::${BucketName}/index.html'
What This Template Does:
• Launches multiple EC2 instances with different tags
• Ensures instances are deployed in multiple Availability Zones
• Assigns instances a security group for controlled access
2. Create a CloudFormation Template for the SSM Document
We now create another CloudFormation template (ssm.yaml) that defines the SSM document for resizing EC2 instances.
ssm.yaml
AWSTemplateFormatVersion: 2010-09-09
# Description: SSM Document for resizing list of EC2 instances
Resources:
SSMDocumentRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ssm.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEC2FullAccess
Path: "/"
document:
Type: AWS::SSM::Document
Properties:
DocumentType: Automation
Content:
schemaVersion: '0.3'
description: 'Resize EC2 instances.'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
AvailabilityZone:
type: String
description: "(Required) The impacted Availability."
default: 'us-east-2a'
TagName:
type: String
description: "(Required) The Tag Name."
default: 'ssm-tag'
TagValue:
type: String
description: "(Required) The Tag Value."
default: 'test-ssm'
InstanceType:
type: String
description: "(Required) The Desired Instance Type."
default: 't2.small'
# If I need to specify another IAMRole
AutomationAssumeRole:
type: String
description: "(Required) The Arn of the role that allows the automation."
default: !GetAtt SSMDocumentRole.Arn
mainSteps:
- name: listInstances
action: aws:executeAwsApi
timeoutSeconds: '60'
inputs:
Service: ec2
Api: DescribeInstances
Filters:
- Name: availability-zone
Values: ["{{ AvailabilityZone }}"]
- Name: instance-state-name
Values: ["running"]
- Name: tag:{{ TagName }}
Values: ["{{ TagValue }}"]
outputs:
- Name: InstanceIds
Selector: "$.Reservations..Instances..InstanceId"
Type: StringList
- name: stopInstances
action: aws:changeInstanceState
onFailure: Continue
inputs:
InstanceIds:
- "{{ listInstances.InstanceIds }}"
DesiredState: stopped
- name: ResizeInstances
action: "aws:executeScript"
inputs:
Runtime: "python3.8"
Handler: resizeInstance
InputPayload:
InstanceIds:
- "{{ listInstances.InstanceIds }}"
Script: |-
def resizeInstance(events, context):
import boto3
client = boto3.client('ec2')
for instance_id in events['InstanceIds']:
#client.modify_instance_attribute(InstanceId=instanceId, InstanceType={'Value': 't2.small',},)
print(instance_id)
print(type(instance_id))
client.modify_instance_attribute(InstanceId=instance_id, Attribute='instanceType', Value='t2.small')
return True
onFailure: Continue
- name: startInstances
action: aws:changeInstanceState
inputs:
InstanceIds:
- "{{ listInstances.InstanceIds }}"
DesiredState: running
isEnd: true
What This Template Does:
• Creates an SSM document that:
• Identifies EC2 instances based on Availability Zone and Tags
• Stops the instances before resizing
• Modifies the instance type using boto3
• Restarts the instances after resizing
• Defines an IAM Role for SSM to interact with EC2
3. Deploy the CloudFormation Templates
- Launch the EC2 Stack
- Deploy the ec2.yaml template to provision instances.
- Ensure the Tag Name and Tag Value are correctly set.
- Launch the SSM Stack
- Deploy the ssm.yaml template to create the SSM document.
- Verify that the IAM role is correctly attached.
4. Execute the SSM Document to Resize Instances
Once both CloudFormation stacks are deployed:
- Go to AWS Systems Manager → Shared Documents
- Click on “Shared with me”
- Select your custom SSM document and click “Execute document”
- Enter the required parameters (Availability Zone, Tag Name, New Instance Type)
- Click Execute
The SSM automation will now:
• Identify instances in the specified Availability Zone & Tag
• Stop instances before resizing
• Modify instance types dynamically
• Restart instances
You should see all EC2 instances updated successfully!
5. Clean Up Resources
To remove the deployed resources:
- Delete the SSM CloudFormation Stack
- Delete the EC2 CloudFormation Stack This ensures that no unintended costs are incurred.
Conclusion
By leveraging AWS Systems Manager (SSM) Documents, we can automate EC2 instance resizing across multiple environments with minimal effort. This solution eliminates manual resizing, ensuring faster and more efficient instance modifications.
Start implementing this in your AWS environment to save time and improve scalability!
Top comments (0)