Instantly share code, notes, and snippets.

Embed
What would you like to do?
Cloudformation basics blog post @ cheppers.com
---
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
DBPassword:
Type: String
NoEcho: true # Specifies that this string should never be printed on the interface or through the API.
AllowedPattern: ^[a-zA-Z0-9_]*$ # Provided strings must match this regex.
MinLength: 8
MaxLength: 32
Description: RDS root password.
Resources:
VPC: # This is the internal resource identifier, an arbitrary string.
Type: AWS::EC2::VPC # VPCs are of the type AWS::EC2::VPC. All AWS resources are in the form of AWS::<Category>::<Resource>
Properties:
CidrBlock: 10.41.0.0/16 # CidrBlock defines the IP range for the VPC.
Tags: # Tags allow us to assign arbitrary key-value pairs to most AWS resources.
- Key: Name
Value: ExampleVPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: ExampleGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway # !Ref is an AWS function that gets a reference to a different resource by internal ID.
VpcId: !Ref VPC # The JSON syntax for this function is { Ref: "InternalResourceName" }
# The return value of Ref is different for each resouce type. Refer to the documentation for more information.
SQSQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Join ["-", [!Ref "AWS::StackName", "task-queue"]]
SubnetAPIZoneA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "a"]] # Like AWS::StackName, AWS::Region is a referencable constant. This will construct the string
# "us-east-1a" in the region us-east-1.
CidrBlock: 10.41.0.0/26 # The IP range to assign to the subnet. For this subnet, we use 10.41.0.0 - 10.41.0.63.
# The IP range must be a subset of the range defined for the VPC.
VpcId: !Ref VPC # Assigns this subnet as a subnet within the VPC defined above.
SubnetAPIZoneB:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "b"]] # us-east-1b
CidrBlock: 10.41.0.64/26 # 10.41.0.64 - 10.41.0.127
VpcId: !Ref VPC
SubnetWorkerZoneA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "a"]] # us-east-1a
CidrBlock: 10.41.1.0/26 # 10.41.1.0 - 10.41.1.63
VpcId: !Ref VPC
SubnetWorkerZoneB:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "b"]] # us-east-1b
CidrBlock: 10.41.1.64/26 # 10.41.1.64 - 10.41.1.127
VpcId: !Ref VPC
SubnetDatabaseZoneA:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "a"]] # us-east-1a
CidrBlock: 10.41.2.0/26 # 10.41.2.0 - 10.41.2.63
VpcId: !Ref VPC
SubnetDatabaseZoneB:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Join ["", [!Ref "AWS::Region", "b"]] # us-east-1b
CidrBlock: 10.41.2.64/26 # 10.41.2.64 - 10.41.2.127
VpcId: !Ref VPC
SubnetGroupDatabase:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: MySQL Database subnet grouping. # Arbitrary
SubnetIds:
- !Ref SubnetDatabaseZoneA
- !Ref SubnetDatabaseZoneB
SecurityGroupDatabase:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ["-", [!Ref "AWS::StackName", "database"]] # Despite being named description, this is more of an ID for the group.
SecurityGroupEgress: # Outgoing traffic rules.
- CidrIp: 0.0.0.0/0 # Allow traffic anywhere.
FromPort: "-1"
ToPort: "-1" # Through any port.
IpProtocol: "-1" # On any IP protocol.
SecurityGroupIngress: # Incoming traffic rules.
- CidrIp: !GetAtt ["VPC", "CidrBlock"] # Retrieves the CidrBlock attribute of the VPC resource. Not all attributes are exposed
# to GetAtt. JSON syntax is { "Fn::GetAtt": ["Resource", "Attribute"] }.
# This essentially means that traffic is allowed from the IP range of the VPC, and
# transitively, from all servers within the VPC.
FromPort: "3306"
ToPort: "3306" # Specifies the port range 3306-3306 (equivalent to port 3306).
IpProtocol: "tcp" # Altogether, this rule specifies that we allow TCP traffic on port 3306 from the VPC
VpcId: !Ref VPC
Database:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: "10" # The allocated storage space for the data in gigabytes.
DBInstanceClass: db.m4.large # The class to use for this instance. Instance classes come in several sizes with different
# optimization focuses - for example, the burst-capable t2 instances or the memory optimized r3s.
# In our case, we use a third generation standard instance.
DBSubnetGroupName: !Ref SubnetGroupDatabase # Reference to the subnet group we created above.
Engine: MySQL # The database engine to use. Options include aurora, mariadb, mysql and postgres.
EngineVersion: "5.7.17" # The version of the database engine - refer to the documentation for the available versions.
MasterUserPassword: !Ref DBPassword # Use the password parameter that we set up above.
MasterUsername: admin # Username for the root account.
MultiAZ: true # Since we're using a subnet group with multiple AZs, we set this to true.
VPCSecurityGroups: # Attaches security groups to this RDS instance.
- !Ref SecurityGroupDatabase
SecurityGroupAPILoadBalancer: # The API's load balancer should be exposed to the world as our API endpoint and should be able to forward traffic to the VPC.
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ["-", [!Ref "AWS::StackName", "api-elb"]]
SecurityGroupEgress: # As load balancers run no custom code, we don't care where it can forward traffic and allow it to go anywhere.
- CidrIp: 0.0.0.0/0
FromPort: "-1"
IpProtocol: "-1"
ToPort: "-1"
SecurityGroupIngress: # We allow HTTP traffic from any IP address.
- CidrIp: 0.0.0.0/0
FromPort: "80"
IpProtocol: tcp
ToPort: "80"
VpcId: !Ref VPC
SecurityGroupAPI: # The API instances should be able to listen to HTTP requests from the ELB and SSH requests from developers.
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ["-", [!Ref "AWS::StackName", "api-elb"]]
SecurityGroupEgress: # We could theoretically restrict outgoing traffic to within the ELB, but this is entirely up to what our app requires.
- CidrIp: 0.0.0.0/0
FromPort: "-1"
IpProtocol: "-1"
ToPort: "-1"
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0 # This first rule specifies that we accept SSH requests (TCP port 22) from any IP address.
FromPort: "22"
IpProtocol: tcp
ToPort: "22"
- FromPort: "3000" # This second rule specifies that we accept requests on port 3000 from the load balancer's security group.
ToPort: "3000" # We do this on port 3000 because the ELB has the ability to change the port of an incoming request.
IpProtocol: tcp # Later, we'll see that the ELB picks up requests on port 80 and forwards them on port 3000.
SourceSecurityGroupId: !Ref SecurityGroupAPILoadBalancer
VpcId: !Ref VPC
SecurityGroupWorker: # The worker instances have no exposed API, so we only care about the ability to SSH in.
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ["-", [!Ref "AWS::StackName", "api-elb"]]
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
FromPort: "-1"
IpProtocol: "-1"
ToPort: "-1"
SecurityGroupIngress:
- CidrIp: 0.0.0.0/0 # This first rule specifies that we accept SSH requests (TCP port 22) from any IP address.
FromPort: "22"
IpProtocol: tcp
ToPort: "22"
VpcId: !Ref VPC
LoadBalancerAPI:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
CrossZone: true # Specifies that the target instances are in multiple availability zones.
HealthCheck: # Defines how the ELB determines if an instance is healthy and may receive requests.
HealthyThreshold: "5" # An unhealthy instance must pass this many health checks in a row to be considered healthy again.
Interval: "15" # The time (in seconds) between two health checks.
Target: HTTP:3000/ping # The protocol, port (and for HTTP, the path) to send a request to. For HTTP, a 200 status code indicates a healthy response.
# This specific target specifies that an HTTP request is sent to port 3000 on path /ping on each instance.
Timeout: "8" # Time to wait for response - timeouts are considered unhealthy.
UnhealthyThreshold: "2" # A healthy instance must fail this many health checks in a row to be considered unhealthy.
Listeners: # Specifies a forwarding rule.
- LoadBalancerPort: "80" # The load balancer listens on port 80;
Protocol: HTTP # uses the HTTP transport protocol for forwarding;
InstancePort: "3000" # and sends it to port 3000 of one of the healthy instances under the ELB.
LoadBalancerName: !Join ["-", [!Ref "AWS::StackName", "api"]]
SecurityGroups: # Here we assign the security group that we created above to the ELB.
- !Ref SecurityGroupAPILoadBalancer
Subnets: # And the subnets as well.
- !Ref SubnetAPIZoneA
- !Ref SubnetAPIZoneB
LaunchConfigurationAPI:
Type: AWS::AutoScaling::LaunchConfiguration
DependsOn:
- SecurityGroupAPI # We set this explicit dependency to enforce security groups to be created before the launch configuration is created.
Properties:
AssociatePublicIpAddress: true # When set, an instance is assigned a public IP address as well as a private one. Required for access from outside the VPC.
BlockDeviceMappings: # Associates storage devices with the instances.
- DeviceName: "/dev/sda1" # The device will be available at this path for mounting.
Ebs: # Specifies that this should be an Elastic Block Storage volume.
VolumeSize: "10" # The storage space on the device, in gigabytes.
VolumeType: gp2 # Specifies the block device type - gp2 is a general-purpose SSD that should suffice for most instances.
ImageId: "ami-d15a75c7" # Specifies the AMI (image) that will be used as the base for these instances.
InstanceType: "t2.micro" # The EC2 instance type to provision. As with database instances, several sizes and purposes are available.
SecurityGroups: # Assigns the security groups specified here to each instance created by the configuration.
- !Ref SecurityGroupAPI
UserData: # UserData is a base64-encoded shell script that runs after the instane is created as root.
Fn::Base64: # Fn::Base64 is a function key that base64-encodes the value string.
!Join ["\n", [
"#!/bin/bash",
"service my-api-service start"
]]
LaunchConfigurationWorker:
Type: AWS::AutoScaling::LaunchConfiguration
DependsOn:
- SecurityGroupWorker
Properties:
AssociatePublicIpAddress: true
BlockDeviceMappings:
- DeviceName: "/dev/sda1"
Ebs:
VolumeSize: "10"
VolumeType: gp2
ImageId: "ami-d15a75c7"
InstanceType: "t2.micro"
SecurityGroups:
- !Ref SecurityGroupWorker
UserData:
Fn::Base64:
!Join ["\n", [
"#!/bin/bash",
"service my-worker-service start"
]]
AutoScalingGroupAPI:
Type: AWS::AutoScaling::AutoScalingGroup
DependsOn: # Setting explicit dependencies will enforce the order in which AWS creates the resources when provisioning and deletes the
- SQSQueue # resources during deprovisioning. There are edge cases where CloudFormation fails to implicitly determine the correct order
- LoadBalancerAPI # and thus it is recommended that you always specify explicit dependencies.
Properties:
DesiredCapacity: 3 # We want 3 instances as the initial number in the autoscaling group.
LaunchConfigurationName: !Ref LaunchConfigurationAPI
LoadBalancerNames:
- !Ref LoadBalancerAPI
MaxSize: 4 # An autoscaling group may never have more instances than its maximum size, even during rolling updates. For that reason, it is
# recommended that the maximum size is always at least 1 greater than the desired capacity.
MinSize: 1 # The autoscaling group will aim to have at least this many instances in service at all time. Prevents dynamic scaledowns
# from bottlenecking resources.
VPCZoneIdentifier: # Specifies the subnets used by this autoscaling group.
- !Ref SubnetAPIZoneA
- !Ref SubnetAPIZoneB
UpdatePolicy: # Attaches a policy that describes how the group behaves during CloudFormation stack updates.
AutoScalingRollingUpdate: # The AutoScalingRollingUpdate policy describes that a rolling update must be performed.
MaxBatchSize: 1 # No more than this many instances may be updated at a time.
MinInstancesInService: 1
PauseTime: PT1M # The amount of time to pause between two batch updates. PT1M means 1 minute.
AutoScalingGroupWorker:
Type: AWS::AutoScaling::AutoScalingGroup
DependsOn:
- SQSQueue
Properties:
DesiredCapacity: 3
LaunchConfigurationName: !Ref LaunchConfigurationWorker
MaxSize: 4
MinSize: 1
VPCZoneIdentifier:
- !Ref SubnetAPIZoneA
- !Ref SubnetAPIZoneB
UpdatePolicy:
AutoScalingRollingUpdate:
MaxBatchSize: 1
MinInstancesInService: 1
PauseTime: PT1M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment