Skip to content

Instantly share code, notes, and snippets.

@0xTim
Created February 17, 2021 17:50
Show Gist options
  • Save 0xTim/095269765d87eacd88103abe1886ad4c to your computer and use it in GitHub Desktop.
Save 0xTim/095269765d87eacd88103abe1886ad4c to your computer and use it in GitHub Desktop.
CF stack for a Vapor app
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation stack for a Vapor app
Parameters:
TaskDefinition:
Type: String
ServiceNameBase:
Type: String
# update with the name of the service
Default: your-servoce
Certificate:
Type: String
Description: ARN of the certification to install on the load balancer
ContainerPort:
Type: Number
Default: 8080
LoadBalancerHTTPSPort:
Type: Number
Default: 443
LoadBalancerHTTPPort:
Type: Number
Default: 80
HealthCheckPath:
Type: String
Default: /hc
# for autoscaling
MinContainers:
Type: Number
Default: 1
# for autoscaling
MaxContainers:
Type: Number
Default: 2
# target CPU utilization (%)
AutoScalingTargetValue:
Type: Number
Default: 50
HostedZoneName:
Type: String
Description: Hosted zone to create a DNS entry for in Route53
EnvironmentName:
Type: String
# Networking
VPC:
Type: AWS::EC2::VPC::Id
SubnetA:
Type: AWS::EC2::Subnet::Id
SubnetB:
Type: AWS::EC2::Subnet::Id
Conditions:
IsProdEnvironment: !Equals [!Ref EnvironmentName, prod]
Resources:
# ECS
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Join ['-', [!Ref ServiceNameBase, Cluster, !Ref EnvironmentName]]
# A role needed for auto scaling
AutoScalingRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, AutoScalingRole]]
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole'
ContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, ContainerSecurityGroup]]
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref ContainerPort
ToPort: !Ref ContainerPort
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
LoadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, LoadBalancerSecurityGroup]]
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !Ref LoadBalancerHTTPSPort
ToPort: !Ref LoadBalancerHTTPSPort
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref LoadBalancerHTTPPort
ToPort: !Ref LoadBalancerHTTPPort
CidrIp: 0.0.0.0/0
Service:
Type: AWS::ECS::Service
# This dependency is needed so that the load balancer is setup correctly in time
DependsOn:
- ListenerHTTPS
Properties:
ServiceName: !Ref ServiceNameBase
Cluster: !Ref Cluster
TaskDefinition: !Ref TaskDefinition
DeploymentConfiguration:
MinimumHealthyPercent: 100
MaximumPercent: 200
DesiredCount: 1
# This may need to be adjusted if the container takes a while to start up
HealthCheckGracePeriodSeconds: 30
LaunchType: FARGATE
PlatformVersion: 1.4.0
NetworkConfiguration:
AwsvpcConfiguration:
# change to DISABLED if you're using private subnets that have access to a NAT gateway
AssignPublicIp: ENABLED
Subnets:
- !Ref SubnetA
- !Ref SubnetB
SecurityGroups:
- !Ref ContainerSecurityGroup
LoadBalancers:
- ContainerName: !Ref ServiceNameBase
ContainerPort: !Ref ContainerPort
TargetGroupArn: !Ref TargetGroup
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 10
# will look for a 200 status code by default unless specified otherwise
HealthCheckPath: !Ref HealthCheckPath
HealthCheckTimeoutSeconds: 5
UnhealthyThresholdCount: 2
HealthyThresholdCount: 2
Name: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, TargetGroup, new]]
Port: !Ref ContainerPort
Protocol: HTTP
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '60' # default is 300
TargetType: ip
VpcId: !Ref VPC
ListenerHTTPS:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref LoadBalancer
Port: !Ref LoadBalancerHTTPSPort
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref Certificate
ListenerHTTPtoHTTPSRedirect:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- RedirectConfig:
Host: "#{host}"
Path: "/#{path}"
Port: !Ref LoadBalancerHTTPSPort
Protocol: "HTTPS"
Query: "#{query}"
StatusCode: HTTP_301
Type: redirect
LoadBalancerArn: !Ref 'LoadBalancer'
Port: !Ref LoadBalancerHTTPPort
Protocol: HTTP
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
# this is the default, but is specified here in case it needs to be changed
- Key: idle_timeout.timeout_seconds
Value: '60'
Name: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, LoadBalancer]]
# "internal" is also an option
Scheme: internet-facing
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
Subnets:
- !Ref SubnetA
- !Ref SubnetB
AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref MinContainers
MaxCapacity: !Ref MaxContainers
ResourceId: !Join ['/', [service, !Ref Cluster, !GetAtt Service.Name]]
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
# "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that allows Application Auto Scaling to modify your scalable target."
RoleARN: !GetAtt AutoScalingRole.Arn
AutoScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Join ['-', [!Ref ServiceNameBase, !Ref EnvironmentName, AutoScalingPolicy]]
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref AutoScalingTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
ScaleInCooldown: 10
ScaleOutCooldown: 10
# Keep things at or lower than 50% CPU utilization, for example
TargetValue: !Ref AutoScalingTargetValue
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Join ['/', [/ecs, !Ref EnvironmentName, !Ref ServiceNameBase]]
RetentionInDays: 14
DNSRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Join ['', [!Ref HostedZoneName, .]]
# Combine the environment and subdomain to create the DNS record. If the environment is prod then leave it out
Name: !Join ['', [!If [IsProdEnvironment, !Ref ServiceNameBase, !Join [., [!Ref ServiceNameBase, !Ref EnvironmentName]]], ., !Ref HostedZoneName, .]]
Type: A
AliasTarget:
DNSName: !GetAtt LoadBalancer.DNSName
HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneID
Outputs:
Endpoint:
Description: Endpoint
Value: !Join ['', ['https://', !Ref DNSRecord]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment