Suppose you’re a developer (like me xD). In that case, you know the value of running applications in the cloud—the ability to scale up and down easily, test your app anywhere, and pay for resources as needed.
AWS EC2 Spot Instances are the best of both worlds—they're just like regular EC2 instances, except you don't need to pay for them upfront, and they're cheaper than regular instances.
The main advantage to spot instances is that you only pay when you use them (rather than paying an upfront fee). But with spot instances, you also get more flexibility—you can move them around pretty easily, which means they work out as a great option for short-term projects or testing. In general, if your project isn't going to last longer than a few weeks or months, and you're not concerned with having access to all the features of your regular instance type (such as the ability to scale it up), then this is probably the best way to go!
If you want to know more about spot instances please take a look at this awesome blog post.
Our Architecture:
VPC
Let's start creating our vpc, piece of cake public and private subnets.
import { IVpc, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
export default class DefaultVpc extends Construct {
public readonly vpc: IVpc;
constructor(scope: Construct, id: string) {
super(scope, id);
this.vpc = new Vpc(this, "my-vpc", {
cidr: "10.0.0.1/24",
subnetConfiguration: [
{
cidrMask: 28,
name: "public subnet",
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 28,
name: "private subnet",
subnetType: SubnetType.PRIVATE_WITH_NAT,
},
],
});
}
}
Application Load Balancer
In this step we need to create an internet facing load balancer
import { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
import { IVpc } from "aws-cdk-lib/aws-ec2";
import { ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
import { Construct } from "constructs";
export default class InternetFacingApplicationLoadBalancer extends Construct {
constructor(scope: Construct, id: string, resources: { vpc: IVpc, ec2AutoScalingGroup: AutoScalingGroup }) {
super(scope, id);
const loadBalancer = new ApplicationLoadBalancer(this, "appLoadBalancer", {
vpc: resources.vpc,
internetFacing: true,
});
const httpListener = loadBalancer.addListener("httpListener", {
port: 80,
open: true,
});
httpListener.addTargets('ApplicationSpotFleet', {
port: 8080,
targets: [resources.ec2AutoScalingGroup],
});
}
}
AutoScaling Group
Now we create the autoscaling, ec2 instances and the wonderful decision to use spot instances.
We are using t4g.micro instances with latest Amazon Linux images. We are allowing all outbound traffic; the desired number of instances is one, while the maximum is 2.
Maximum spot price is set to 0.007, if the sport price goes up, for example to 0.008, then we have no instances. There are ways to address this, but for this example I'll only use it in this way.
import { Duration } from "aws-cdk-lib";
import { AutoScalingGroup, HealthCheck } from "aws-cdk-lib/aws-autoscaling";
import {
AmazonLinuxGeneration,
AmazonLinuxImage,
InstanceClass,
InstanceSize,
InstanceType,
IVpc,
} from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
export default class ApplicationAutoScalingGroup extends Construct {
public readonly autoScalingGroup: AutoScalingGroup;
constructor(
scope: Construct,
id: string,
resources: { vpc: IVpc }
) {
super(scope, id);
const applicationAutoScalingGroup = new AutoScalingGroup(this, "AutoScalingGroup", {
vpc: resources.vpc,
instanceType: InstanceType.of(
InstanceClass.BURSTABLE4_GRAVITON,
InstanceSize.MICRO
),
machineImage: new AmazonLinuxImage({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
}),
allowAllOutbound: true,
maxCapacity: 2,
minCapacity: 1,
desiredCapacity: 1,
spotPrice: "0.007", // $0.0032 per Hour when writing, $0.0084 per Hour on-demand
healthCheck: HealthCheck.ec2(),
});
applicationAutoScalingGroup.scaleOnCpuUtilization("CpuScaling", {
targetUtilizationPercent: 50,
cooldown: Duration.minutes(1),
estimatedInstanceWarmup: Duration.minutes(1),
});
this.autoScalingGroup = applicationAutoScalingGroup;
}
}
Finally our stack will be something like that:
Stack
import { StackProps, Environment, Stack } from 'aws-cdk-lib'
import { Construct } from 'constructs'
import InternetFacingApplicationLoadBalancer from '../lib/application-load-balancer/internet-facing-application-load-balancer'
import ApplicationAutoScalingGroup from '../lib/ec2/auto-scaling-group'
import DefaultVpc from '../lib/vpc/default-vpc'
export interface IStackProps extends StackProps {
variables?: any
env: Environment
}
export class ApplicationIntegrationStack extends Stack {
constructor(scope: Construct, id: string, props: IStackProps) {
super(scope, id, props)
const { vpc } = new DefaultVpc(this, 'DefaultVpc')
const { autoScalingGroup } = new ApplicationAutoScalingGroup(this, 'ApplicationAutoScalingGroup', { vpc })
new InternetFacingApplicationLoadBalancer(this, 'InternetFacingApplicationLoadBalancer', { vpc, ec2AutoScalingGroup: autoScalingGroup })
}
}
If you want to take a deep dive into this topic I strongly recommend this re:invent video about spot instances.
That's all folks!
Oldest comments (4)
Amazing bro!!
thaaaaanks my bro
Just found your posts man. Amazing work, I will definitely take a look in the upcoming days! Cheers
thanks for your words mate, I really appreciate this!
your blog posts about caching helped me a lot