DEV Community

Nurul Ramadhona for AWS Community Builders

Posted on • Edited on

AWS Auto Scaling Features to Optimize Cost and Ensure Availability

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?

  1. How about the cost? (we can't deny that it always be number one)

  2. What are the benefits?

  3. Many more ...

The following solutions can be your "weapon" when it comes to the cloud, but how?

EC2 Auto Scaling

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.

Full ASG

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 
Enter fullscreen mode Exit fullscreen mode

(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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Main Points

1. Use Mixed Instances (On-Demand + Spot) To Optimize Cost

 MixedInstancesPolicy: InstancesDistribution: OnDemandAllocationStrategy: prioritized OnDemandPercentageAboveBaseCapacity: 30 SpotAllocationStrategy: capacity-optimized SpotMaxPrice: 0.003 
Enter fullscreen mode Exit fullscreen mode

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:

Pricing

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) 
Enter fullscreen mode Exit fullscreen mode

Pricing:

Priority

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 
Enter fullscreen mode Exit fullscreen mode

From the settings, the first creation of this architecture is going to be:

ASG 1

Output 1

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 
Enter fullscreen mode Exit fullscreen mode

For more information, click here!

3. Use Launch Template Instead of Launch Configuration

Launch Template

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

The instance type is t3.small based on the best match with the maximum spot price from the priority.

ASG 2

Output 2

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 
Enter fullscreen mode Exit fullscreen mode

ASG 3

Output 3

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Output 4

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 
Enter fullscreen mode Exit fullscreen mode
$ 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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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:

ASG 5

Here's the output!

Output 5

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 $ 
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)