AWS Auto Scaling Features to Optimize Cost and Ensure Availability
Nurul Ramadhona
Posted on August 18, 2022
Cloud Computing may be a solution for your on-premises problem but not everyone knows the cloud, right? Let's say you find a solution for your application that can be done using the cloud but a solution to "persuade" your boss may seem a bit harder :)
If you agree with me, please comment "Yes" so I can hear your voice :) Just kidding!
Are you the one who is confused with so many questions?
How about the cost? (we can't deny that it always be number one)
What are the benefits?
Many more ...
The following solutions can be your "weapon" when it comes to the cloud, but how?
Always Available
One of the biggest benefits of using the cloud is the provider is always able to meet our needs. Let's say you need a VM right now! You can directly access your AWS console and create an EC2 instance. It will be available and running as soon as possible after you create it. One problem is solved.
Then, what if we need more features? Let's say you want to use LightSail to run your WordPress instantly but it seems like it's not available in your region (for example Jakarta)! You can choose the nearest region where the service is already activated (for example Singapore). The second problem is solved and you can access your WordPress dashboard now.
Or maybe you want to have a backup for your application when there is a problem in one area? You can activate multi-AZ or region, third problem is solved.
But what if our product/application becomes viral and accessed from various countries? You can use CloudFront as CDN.
That's how cloud availability solves our problem "in general", I can't say it's the best AWS solution since AWS has so many services. So when we need the resources as soon as possible, we don't need to buy and look for the best brand for our devices.
Ease of Scaling
Let's say we're newbies in the cloud and we're just moving from on-premises culture. So EC2 will bring you easily to get to know about cloud environment. We all know we can create as many EC2 instances as we need (20 per region but can be increased by request) but one thing that really important and we can't forget about is cost. It can be so high when you don't know how to optimize based on your need (especially when you use on-demand instances).
Alright! There are so many ways we can scale our applications and optimize the cost, but here I will take two services that can be a basis for how we do it with AWS.
In this post, I'll create AWS Auto Scaling Group and Application Load Balancer using CloudFormation. All operations will be done through CLI, so make sure you have installed AWS CLI and set up the credentials. Note: You can do this through the web console if you want.
Network details (you can change based on your need):
VPC CIDR: 172.16.0.0/16
Subnet 1: 172.16.1.0/28
Subnet 2: 172.16.15.0/28
Subnet 3: 172.16.30.0/28
(Sorry for a little typo, subnet 2 should be 172.16.1.16/28
and subnet 3 should be 172.16.1.32/28
for /28
usage but it's okay because the above networks still work)
Here's the whole content of the CloudFormation template named cfn-asg.yaml
! I'll tell you the main points later.
AWSTemplateFormatVersion: 2010-09-09
Parameters:
AmazonLinux2LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
EnvironmentName:
Type: String
Default: DhonaASGTemplate
SpotInstanceType1:
Type: String
Default: t3.large
SpotInstanceType2:
Type: String
Default: t3.medium
SpotInstanceType3:
Type: String
Default: t3.small
SpotInstanceType4:
Type: String
Default: t3.micro
SpotInstanceType5:
Type: String
Default: t3.nano
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.16.0.0/16
EnableDnsHostnames: true
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName}
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName}
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId:
Ref: InternetGateway
VpcId:
Ref: VPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName} Public Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId:
Ref: PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: PublicRouteTable
SubnetId:
Ref: PublicSubnet1
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
AvailabilityZone: ap-southeast-3a
CidrBlock: 172.16.1.0/28
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName} Public Subnet (AZ1)
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: PublicRouteTable
SubnetId:
Ref: PublicSubnet2
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
AvailabilityZone: ap-southeast-3b
CidrBlock: 172.16.15.0/28
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName} Public Subnet (AZ2)
PublicSubnet3RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: PublicRouteTable
SubnetId:
Ref: PublicSubnet3
PublicSubnet3:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
AvailabilityZone: ap-southeast-3c
CidrBlock: 172.16.30.0/28
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName} Public Subnet (AZ3)
DhonaWebAppAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
DependsOn: InternetGatewayAttachment
Properties:
CapacityRebalance: true
HealthCheckType: ELB
HealthCheckGracePeriod: 300
MinSize: "3"
MaxSize: "5"
DesiredCapacity: "3"
VPCZoneIdentifier:
- Ref: PublicSubnet1
- Ref: PublicSubnet2
- Ref: PublicSubnet3
MixedInstancesPolicy:
InstancesDistribution:
OnDemandAllocationStrategy: prioritized
OnDemandPercentageAboveBaseCapacity: 30
SpotAllocationStrategy: capacity-optimized
SpotMaxPrice: 0.003
LaunchTemplate:
LaunchTemplateSpecification:
LaunchTemplateId:
Ref: DhonaWebAppLaunchTemplate
Version:
Fn::GetAtt:
- DhonaWebAppLaunchTemplate
- LatestVersionNumber
Overrides:
- InstanceType:
Ref: SpotInstanceType1
- InstanceType:
Ref: SpotInstanceType2
- InstanceType:
Ref: SpotInstanceType3
- InstanceType:
Ref: SpotInstanceType4
- InstanceType:
Ref: SpotInstanceType5
TargetGroupARNs:
- Ref: DhonaWebAppTargetGroup
Tags:
- Key: Name
Value:
Fn::Sub: ${EnvironmentName}
PropagateAtLaunch: true
DhonaWebAppLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
DependsOn: InternetGatewayAttachment
Properties:
LaunchTemplateData:
ImageId:
Ref: AmazonLinux2LatestAmiId
SecurityGroupIds:
- Ref: DhonaWebAppEC2SecurityGroup
UserData:
Fn::Base64: >
#!/bin/bash
yum -y install httpd
echo "hello world!
My instance-id is $(curl -s http://169.254.169.254/latest/meta-data/instance-id)
My instance type is $(curl -s http://169.254.169.254/latest/meta-data/instance-type)
I'm on Availability Zone $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)" > /var/www/html/index.html
service httpd start
DhonaWebAppELBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ELB
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 80
ToPort: 80
VpcId:
Ref: VPC
DhonaWebAppEC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for EC2 ASG
SecurityGroupIngress:
- SourceSecurityGroupId:
Ref: DhonaWebAppELBSecurityGroup
IpProtocol: tcp
FromPort: 80
ToPort: 80
VpcId:
Ref: VPC
DhonaWebAppLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn: InternetGatewayAttachment
Properties:
SecurityGroups:
- Ref: DhonaWebAppELBSecurityGroup
Subnets:
- Ref: PublicSubnet1
- Ref: PublicSubnet2
- Ref: PublicSubnet3
DhonaWebAppTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Port: "80"
Protocol: HTTP
VpcId:
Ref: VPC
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: DhonaWebAppLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn:
Ref: DhonaWebAppTargetGroup
Outputs:
URL:
Value: !Join
- ''
- - 'http://'
- !GetAtt
- DhonaWebAppLoadBalancer
- DNSName
Let's create the CloudFormation stack!
$ aws cloudformation create-stack --stack-name cfn-asg --template-body file://cfn-asg.yaml
StackId: arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-asg/438781a0-1e7e-11ed-b1ba-06ff26b14cdc
$ aws cloudformation describe-stacks --stack-name cfn-asg | grep "OutputValue\|StackStatus"
OutputValue: http://cfn-a-Dhona-1NM3JA9AKJ0BE-1240836435.ap-southeast-3.elb.amazonaws.com
StackStatus: CREATE_COMPLETE
Main Points
1. Use Mixed Instances (On-Demand + Spot) To Optimize Cost
MixedInstancesPolicy:
InstancesDistribution:
OnDemandAllocationStrategy: prioritized
OnDemandPercentageAboveBaseCapacity: 30
SpotAllocationStrategy: capacity-optimized
SpotMaxPrice: 0.003
If your workload can be interrupted (the process can be repeated when it fails), you can use spot instances. Why? Because you will get a big discount instead of using On-Demand. Here's the comparison:
As you can see, it's almost 70% compared to the On-Demand instance but one thing you should know is it can be interrupted when your bid price is lower than the actual price. That's why you should know which workloads are suitable for this instance type.
Then here I use 5 instance types based on priority:
Overrides:
- InstanceType:
Ref: SpotInstanceType1 (t3.large)
- InstanceType:
Ref: SpotInstanceType2 (t3.medium)
- InstanceType:
Ref: SpotInstanceType3 (t3.small)
- InstanceType:
Ref: SpotInstanceType4 (t3.micro)
- InstanceType:
Ref: SpotInstanceType5 (t3.nano)
Pricing:
From the details above, I choose to use:
On-demand instance type based on top priority (t3.large) with percentage 30 which means 30% on-demand and 70% for spot instances. So if I use desired and minimum capacity 3, 1 on-demand instance and 2 spot instances should be running.
Spot instances with a maximum price is 0.003 and based on all instance types I used, t3.nano meets the criteria.
$ aws ec2 describe-instances --filters Name=instance-state-name,Values=running --query 'Reservations[].Instances[].{ID:InstanceId}'
- ID: i-02323c2a2be9759f2
- ID: i-004b63ceec33e2bb5
- ID: i-0b11803cfd9e623bb
From the settings, the first creation of this architecture is going to be:
2. Activate Capacity Rebalance To Ensure Availability
It helps you to create a new spot instance based on the EC2 instance rebalance recommendation two minutes before your current spot instance is interrupted.
CapacityRebalance: true
For more information, click here!
3. Use Launch Template Instead of Launch Configuration
For more information, click here.
Alright! Those are all the main points. Let's play some scenarios so we can understand them better!
1. Change Maximum Spot Price (Increase)
On the first change, we will increase the maximum spot price and terminate one spot instance to see which instance type will be chosen.
Note: You can make a change to the same template directly. Here I create a new template to show you the difference between them.
$ diff cfn-asg.yaml cfn-asg-2.yaml
145c145
< SpotMaxPrice: 0.003
---
> SpotMaxPrice: 0.01
Update the stack:
$ aws cloudformation update-stack --stack-name cfn-asg --template-body file://cfn-asg-2.yaml
StackId: arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-asg/438781a0-1e7e-11ed-b1ba-06ff26b14cdc
$ aws cloudformation describe-stacks --stack-name cfn-asg | grep "OutputValue\|StackStatus"
OutputValue: http://cfn-a-Dhona-1NM3JA9AKJ0BE-1240836435.ap-southeast-3.elb.amazonaws.com
StackStatus: UPDATE_COMPLETE
Terminate one spot instance:
$ aws ec2 describe-spot-instance-requests --query "SpotInstanceRequests[*].{ID:InstanceId}"
- ID: i-0b11803cfd9e623bb
- ID: i-02323c2a2be9759f2
$ aws ec2 terminate-instances --instance-ids i-02323c2a2be9759f2
TerminatingInstances:
- CurrentState:
Code: 32
Name: shutting-down
InstanceId: i-02323c2a2be9759f2
PreviousState:
Code: 16
Name: running
Here's the output when it's considered as UNHEALTHY and replaced with the new instance:
- AutoScalingGroupName: cfn-asg-DhonaWebAppAutoScalingGroup-NQBH0P5D9DNF
AvailabilityZone: ap-southeast-3a
HealthStatus: HEALTHY
InstanceId: i-00c3754aed2929ad9
InstanceType: t3.small
LaunchTemplate:
LaunchTemplateId: lt-0ab4b34b94589f554
LaunchTemplateName: DhonaWebAppLaunchTemplate_6Nl7i61kH5qJ
Version: '1'
LifecycleState: InService
ProtectedFromScaleIn: false
- AutoScalingGroupName: cfn-asg-DhonaWebAppAutoScalingGroup-NQBH0P5D9DNF
AvailabilityZone: ap-southeast-3a
HealthStatus: UNHEALTHY
InstanceId: i-02323c2a2be9759f2
InstanceType: t3.nano
LaunchTemplate:
LaunchTemplateId: lt-0ab4b34b94589f554
LaunchTemplateName: DhonaWebAppLaunchTemplate_6Nl7i61kH5qJ
Version: '1'
LifecycleState: Terminating
ProtectedFromScaleIn: false
The instance type is t3.small based on the best match with the maximum spot price from the priority.
But what if we terminate the on-demand instance? Will the instance type be changed? The answer is No because the maximum spot price doesn't affect the on-demand setting.
$ aws ec2 terminate-instances --instance-ids i-004b63ceec33e2bb5
TerminatingInstances:
- CurrentState:
Code: 32
Name: shutting-down
InstanceId: i-004b63ceec33e2bb5
PreviousState:
Code: 16
Name: running
2. Change The On-Demand Allocation Strategy
By changing it to the lowest-price
, it will ignore the priority and choose the lowest price of all available instance types. In this case, it's t3.nano
.
$ diff cfn-asg-2.yaml cfn-asg-3.yaml
142c142
< OnDemandAllocationStrategy: prioritized
---
> OnDemandAllocationStrategy: lowest-price
Update the stack:
$ aws cloudformation update-stack --stack-name cfn-asg --template-body file://cfn-asg-3.yaml
StackId: arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-asg/438781a0-1e7e-11ed-b1ba-06ff26b14cdc
Terminate the on-demand instance:
$ aws ec2 terminate-instances --instance-ids i-018469de2746c9432
TerminatingInstances:
- CurrentState:
Code: 32
Name: shutting-down
InstanceId: i-018469de2746c9432
PreviousState:
Code: 16
Name: running
As we expected, the new on-demand instance type is t3.nano
.
- AutoScalingGroupName: cfn-asg-DhonaWebAppAutoScalingGroup-NQBH0P5D9DNF
AvailabilityZone: ap-southeast-3b
HealthStatus: HEALTHY
InstanceId: i-0f3ab2d7fa671a597
InstanceType: t3.nano
LaunchTemplate:
LaunchTemplateId: lt-0ab4b34b94589f554
LaunchTemplateName: DhonaWebAppLaunchTemplate_6Nl7i61kH5qJ
Version: '1'
LifecycleState: InService
ProtectedFromScaleIn: false
3. Make a Change To Launch Template (Create Version)
We have set to use the latest version of the launch template when there is a change, right? So now we will try to prove it.
LaunchTemplate:
LaunchTemplateSpecification:
LaunchTemplateId:
Ref: DhonaWebAppLaunchTemplate
Version:
Fn::GetAtt:
- DhonaWebAppLaunchTemplate
- LatestVersionNumber
$ diff cfn-asg-3.yaml cfn-asg-4.yaml
193c193,195
< I'm on Availability Zone $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)" > /var/www/html/index.html
---
> I'm on Availability Zone $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)
>
> This is additional line for updating launch template" > /var/www/html/index.html
Update the stack:
$ aws cloudformation update-stack --stack-name cfn-asg --template-body file://cfn-asg-4.yaml
StackId: arn:aws:cloudformation:ap-southeast-3:0123456789:stack/cfn-asg/438781a0-1e7e-11ed-b1ba-06ff26b14cdc
Then, let's terminate one spot instance again!
$ aws ec2 describe-spot-instance-requests --filters Name=state,Values=active --query "SpotInstanceRequests[*].{ID:InstanceId}"
- ID: i-00c3754aed2929ad9
- ID: i-0b11803cfd9e623bb
$ aws ec2 terminate-instances --instance-ids i-0b11803cfd9e623bb
TerminatingInstances:
- CurrentState:
Code: 32
Name: shutting-down
InstanceId: i-0b11803cfd9e623bb
PreviousState:
Code: 16
Name: running
We're still using the same Auto Scaling Group with the right capacity but in a different version of the launch template. In this case, it's the latest. So that's why we are suggested to use a launch template instead of a launch configuration. By using a launch configuration, we have to create a new one when we need to make a change.
Now, the latest architecture is:
Here's the output!
Alright! Those are all the changes we have made to the architecture. Please note that all the changes will be applied to the new instances, not to the current running instances. So for example when one instance is interrupted, the changes will be applied to the new instance created.
Cleanup
If you already followed all the instructions above, you can remove all of them only by deleting the stack. That's why we use CloudFormation, just to make it simple :)
$ aws cloudformation delete-stack --stack-name cfn-asg
$ aws autoscaling describe-auto-scaling-groups
AutoScalingGroups: []
$ aws ec2 describe-spot-instance-requests --filters Name=state,Values=active
SpotInstanceRequests: []
$ aws ec2 describe-instances --filters Name=instance-state-name,Values=running
Reservations: []
$ aws cloudformation describe-stacks --stack-name cfn-asg
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id cfn-asg does not exist
$
In this post, I just show you some features that you can use with Auto Scaling. This is not the all features Auto Scaling has. You can mix it with Reserved Instance, use dynamic scaling, and many more. Here I'm not doing it all because this is just a "personal" demo, I don't need too big resources for very high workloads or long-term subscriptions.
That's it for now! Thank you for coming and I'm looking forward to your feedback. Follow me to get notified when my new post is published!
Posted on August 18, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.