Created
March 16, 2023 15:53
-
-
Save 3sky/1ad5a165f5aaece32e7fe8a71ee9d797 to your computer and use it in GitHub Desktop.
Full 3-tier ECS CloudFormation Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
AWSTemplateFormatVersion: 2010-09-09 | |
Description: An example CloudFormation template for Fargate. | |
Parameters: | |
EnvironmentName: | |
Description: An environment name that is prefixed to resource names | |
Type: String | |
VpcCIDR: | |
Description: Please enter the IP range (CIDR notation) for this VPC | |
Type: String | |
Default: 10.192.0.0/16 | |
PublicSubnet1CIDR: | |
Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone | |
Type: String | |
Default: 10.192.10.0/24 | |
PublicSubnet2CIDR: | |
Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone | |
Type: String | |
Default: 10.192.20.0/24 | |
PrivateSubnet1CIDR: | |
Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone | |
Type: String | |
Default: 10.192.11.0/24 | |
PrivateSubnet2CIDR: | |
Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone | |
Type: String | |
Default: 10.192.21.0/24 | |
PrivateSubnet3CIDR: | |
Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone (DB Layer) | |
Type: String | |
Default: 10.192.12.0/24 | |
PrivateSubnet4CIDR: | |
Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone (DB Layer) | |
Type: String | |
Default: 10.192.22.0/24 | |
Image: | |
Description: Enter the name of image used in the task | |
Type: String | |
Default: public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest | |
ServiceName: | |
Description: Name of ECS servcie | |
Type: String | |
Default: MyService | |
ContainerPort: | |
Type: Number | |
Default: 80 | |
LoadBalancerPort: | |
Type: Number | |
Default: 80 | |
HealthCheckPath: | |
Type: String | |
Default: / | |
MinContainers: | |
Type: Number | |
Default: 2 | |
MaxContainers: | |
Type: Number | |
Default: 4 | |
AutoScalingTargetValue: | |
Type: Number | |
Default: 50 | |
Resources: | |
VPC: | |
Type: AWS::EC2::VPC | |
Properties: | |
CidrBlock: !Ref VpcCIDR | |
EnableDnsSupport: true | |
EnableDnsHostnames: true | |
Tags: | |
- Key: Name | |
Value: !Ref EnvironmentName | |
InternetGateway: | |
Type: AWS::EC2::InternetGateway | |
Properties: | |
Tags: | |
- Key: Name | |
Value: !Ref EnvironmentName | |
NATGateway: | |
Type: AWS::EC2::NatGateway | |
Properties: | |
AllocationId: | |
Fn::GetAtt: | |
- EIP | |
- AllocationId | |
SubnetId: | |
Ref: PublicSubnet1 | |
Tags: | |
- Key: stack | |
Value: production | |
EIP: | |
DependsOn: | |
- VPC | |
Type: AWS::EC2::EIP | |
Properties: | |
Domain: vpc | |
InternetGatewayAttachment: | |
Type: AWS::EC2::VPCGatewayAttachment | |
Properties: | |
InternetGatewayId: !Ref InternetGateway | |
VpcId: !Ref VPC | |
PublicSubnet1: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [0, !GetAZs ""] | |
CidrBlock: !Ref PublicSubnet1CIDR | |
MapPublicIpOnLaunch: true | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} Public Subnet (AZ1) | |
PublicSubnet2: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [1, !GetAZs ""] | |
CidrBlock: !Ref PublicSubnet2CIDR | |
MapPublicIpOnLaunch: true | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} Public Subnet (AZ2) | |
PrivateSubnet1: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [0, !GetAZs ""] | |
CidrBlock: !Ref PrivateSubnet1CIDR | |
MapPublicIpOnLaunch: false | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} App Private Subnet (AZ1) | |
PrivateSubnet2: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [1, !GetAZs ""] | |
CidrBlock: !Ref PrivateSubnet2CIDR | |
MapPublicIpOnLaunch: false | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} App Private Subnet (AZ2) | |
PrivateSubnet3: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [0, !GetAZs ""] | |
CidrBlock: !Ref PrivateSubnet3CIDR | |
MapPublicIpOnLaunch: false | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} DB Private Subnet (AZ1) | |
PrivateSubnet4: | |
Type: AWS::EC2::Subnet | |
Properties: | |
VpcId: !Ref VPC | |
AvailabilityZone: !Select [1, !GetAZs ""] | |
CidrBlock: !Ref PrivateSubnet4CIDR | |
MapPublicIpOnLaunch: false | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} DB Private Subnet (AZ2) | |
PublicRouteTable: | |
Type: AWS::EC2::RouteTable | |
Properties: | |
VpcId: !Ref VPC | |
Tags: | |
- Key: Name | |
Value: !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 | |
PublicSubnet2RouteTableAssociation: | |
Type: AWS::EC2::SubnetRouteTableAssociation | |
Properties: | |
RouteTableId: !Ref PublicRouteTable | |
SubnetId: !Ref PublicSubnet2 | |
PrivateRouteTable1: | |
Type: AWS::EC2::RouteTable | |
Properties: | |
VpcId: !Ref VPC | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} Private Routes (AZ1) | |
DefaultPrivateRoute1: | |
Type: AWS::EC2::Route | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable1 | |
DestinationCidrBlock: 0.0.0.0/0 | |
NatGatewayId: !Ref NATGateway | |
PrivateSubnet1RouteTableAssociation: | |
Type: AWS::EC2::SubnetRouteTableAssociation | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable1 | |
SubnetId: !Ref PrivateSubnet1 | |
PrivateRouteTable2: | |
Type: AWS::EC2::RouteTable | |
Properties: | |
VpcId: !Ref VPC | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} Private Routes (AZ2) | |
DefaultPrivateRoute2: | |
Type: AWS::EC2::Route | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable2 | |
DestinationCidrBlock: 0.0.0.0/0 | |
NatGatewayId: !Ref NATGateway | |
PrivateSubnet2RouteTableAssociation: | |
Type: AWS::EC2::SubnetRouteTableAssociation | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable2 | |
SubnetId: !Ref PrivateSubnet2 | |
PrivateRouteTable3: | |
Type: AWS::EC2::RouteTable | |
Properties: | |
VpcId: !Ref VPC | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} DB Private Routes (AZ1) | |
DefaultPrivateRoute3: | |
Type: AWS::EC2::Route | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable3 | |
DestinationCidrBlock: 0.0.0.0/0 | |
NatGatewayId: !Ref NATGateway | |
PrivateSubnet3RouteTableAssociation: | |
Type: AWS::EC2::SubnetRouteTableAssociation | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable3 | |
SubnetId: !Ref PrivateSubnet3 | |
PrivateRouteTable4: | |
Type: AWS::EC2::RouteTable | |
Properties: | |
VpcId: !Ref VPC | |
Tags: | |
- Key: Name | |
Value: !Sub ${EnvironmentName} DB Private Routes (AZ1) | |
DefaultPrivateRoute4: | |
Type: AWS::EC2::Route | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable4 | |
DestinationCidrBlock: 0.0.0.0/0 | |
NatGatewayId: !Ref NATGateway | |
PrivateSubnet4RouteTableAssociation: | |
Type: AWS::EC2::SubnetRouteTableAssociation | |
Properties: | |
RouteTableId: !Ref PrivateRouteTable4 | |
SubnetId: !Ref PrivateSubnet4 | |
Cluster: | |
Type: AWS::ECS::Cluster | |
Properties: | |
ClusterName: !Sub "${ServiceName}Cluster" | |
TaskDefinition: | |
Type: AWS::ECS::TaskDefinition | |
Properties: | |
Family: !Sub "${ServiceName}TaskDefinition" | |
# awsvpc is required for Fargate | |
NetworkMode: awsvpc | |
RequiresCompatibilities: | |
- FARGATE | |
Cpu: 256 | |
Memory: 0.5GB | |
ExecutionRoleArn: !GetAtt ExecutionRole.Arn | |
TaskRoleArn: !Ref TaskRole | |
ContainerDefinitions: | |
- Name: !Ref ServiceName | |
Image: !Ref Image | |
PortMappings: | |
- ContainerPort: !Ref ContainerPort | |
LogConfiguration: | |
LogDriver: awslogs | |
Options: | |
awslogs-region: !Ref AWS::Region | |
awslogs-group: !Ref LogGroup | |
awslogs-stream-prefix: ecs | |
ExecutionRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Sub "${ServiceName}ExecutionRole" | |
AssumeRolePolicyDocument: | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: ecs-tasks.amazonaws.com | |
Action: "sts:AssumeRole" | |
ManagedPolicyArns: | |
- "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" | |
TaskRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Sub "${ServiceName}TaskRole" | |
AssumeRolePolicyDocument: | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: ecs-tasks.amazonaws.com | |
Action: "sts:AssumeRole" | |
Path: / | |
Policies: | |
- PolicyName: AuroraIAMAcess | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: Allow | |
Action: "rds-db:connect" | |
Resource: | |
- !Sub "arn:${AWS::Partition}:rds:${AWS::Region}:${AWS::AccountId}:cluster:${AuroraCluster}" | |
ContainerRegistry: | |
Type: AWS::ECR::Repository | |
Properties: | |
RepositoryName: !Ref ServiceName | |
AutoScalingRole: | |
Type: AWS::IAM::Role | |
Properties: | |
RoleName: !Sub "${ServiceName}-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: !Sub "${ServiceName}-ContainerSecurityGroup" | |
VpcId: !Ref VPC | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: !Ref ContainerPort | |
ToPort: !Ref ContainerPort | |
SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup | |
LoadBalancerSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupDescription: !Sub "${ServiceName}-LoadBalancerSecurityGroup" | |
VpcId: !Ref VPC | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: !Ref LoadBalancerPort | |
ToPort: !Ref LoadBalancerPort | |
CidrIp: 0.0.0.0/0 | |
AuroraSecurityGroup: | |
Type: AWS::EC2::SecurityGroup | |
Properties: | |
GroupDescription: !Sub "${ServiceName}-AuroraSecurityGroup" | |
VpcId: !Ref VPC | |
SecurityGroupIngress: | |
- IpProtocol: tcp | |
FromPort: 3306 | |
ToPort: 3306 | |
SourceSecurityGroupId: !Ref ContainerSecurityGroup | |
Service: | |
DependsOn: | |
- LoadBalancer | |
Type: AWS::ECS::Service | |
Properties: | |
ServiceName: !Ref ServiceName | |
Cluster: !Ref Cluster | |
TaskDefinition: !Ref TaskDefinition | |
DeploymentConfiguration: | |
MinimumHealthyPercent: 100 | |
MaximumPercent: 200 | |
DesiredCount: 2 | |
HealthCheckGracePeriodSeconds: 30 | |
LaunchType: FARGATE | |
NetworkConfiguration: | |
AwsvpcConfiguration: | |
# change to DISABLED if you're using private subnets that have access to a NAT gateway | |
AssignPublicIp: DISABLED | |
Subnets: | |
- !Ref PrivateSubnet1 | |
- !Ref PrivateSubnet2 | |
SecurityGroups: | |
- !Ref ContainerSecurityGroup | |
LoadBalancers: | |
- ContainerName: !Ref ServiceName | |
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: !Sub "${ServiceName}-TargetGroup" | |
Port: !Ref ContainerPort | |
Protocol: HTTP | |
TargetGroupAttributes: | |
- Key: deregistration_delay.timeout_seconds | |
Value: 60 # default is 300 | |
TargetType: ip | |
VpcId: !Ref VPC | |
Listener: | |
Type: AWS::ElasticLoadBalancingV2::Listener | |
Properties: | |
DefaultActions: | |
- TargetGroupArn: !Ref TargetGroup | |
Type: forward | |
LoadBalancerArn: !Ref LoadBalancer | |
Port: !Ref LoadBalancerPort | |
Protocol: HTTP | |
LoadBalancer: | |
Type: AWS::ElasticLoadBalancingV2::LoadBalancer | |
Properties: | |
Type: application | |
LoadBalancerAttributes: | |
- Key: idle_timeout.timeout_seconds | |
Value: 60 | |
Name: !Sub "${ServiceName}-LoadBalancer" | |
Scheme: internet-facing | |
SecurityGroups: | |
- !Ref LoadBalancerSecurityGroup | |
Subnets: | |
- !Ref PublicSubnet1 | |
- !Ref PublicSubnet2 | |
LogGroup: | |
Type: AWS::Logs::LogGroup | |
Properties: | |
LogGroupName: !Sub "/ecs/${ServiceName}-TaskDefinition" | |
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 | |
RoleARN: !GetAtt AutoScalingRole.Arn | |
AutoScalingPolicy: | |
Type: AWS::ApplicationAutoScaling::ScalingPolicy | |
Properties: | |
PolicyName: !Sub "${ServiceName}-AutoScalingPolicy" | |
PolicyType: TargetTrackingScaling | |
ScalingTargetId: !Ref AutoScalingTarget | |
TargetTrackingScalingPolicyConfiguration: | |
PredefinedMetricSpecification: | |
PredefinedMetricType: ECSServiceAverageCPUUtilization | |
ScaleInCooldown: 10 | |
ScaleOutCooldown: 10 | |
TargetValue: !Ref AutoScalingTargetValue | |
AuroraSecret: | |
Type: "AWS::SecretsManager::Secret" | |
DeletionPolicy: Delete | |
Properties: | |
Name: MySecretForAppA | |
Description: "This secret has a dynamically generated secret password." | |
GenerateSecretString: | |
SecretStringTemplate: '{"username": "DBUsername"}' | |
GenerateStringKey: "password" | |
PasswordLength: 40 | |
ExcludeCharacters: '"@/\' | |
Tags: | |
- Key: Service | |
Value: !Sub "${ServiceName}-Aurora-Secret" | |
DBSubnetGroup: | |
Type: AWS::RDS::DBSubnetGroup | |
Properties: | |
DBSubnetGroupDescription: Aurora Serverless DBSubnet group | |
DBSubnetGroupName: !Sub "${ServiceName}-DBSubnetGroup" | |
SubnetIds: | |
- !Ref PrivateSubnet3 | |
- !Ref PrivateSubnet4 | |
AuroraCluster: | |
DependsOn: | |
- "AuroraSecret" | |
Type: AWS::RDS::DBCluster | |
Properties: | |
EnableIAMDatabaseAuthentication: true | |
MasterUsername: !Sub "{{resolve:secretsmanager:${AuroraSecret}::username}}" | |
MasterUserPassword: !Sub "{{resolve:secretsmanager:${AuroraSecret}::password}}" | |
DatabaseName: RANDOMNAME | |
Engine: aurora | |
EngineMode: serverless | |
ScalingConfiguration: | |
AutoPause: true | |
MaxCapacity: 4 | |
MinCapacity: 2 | |
SecondsUntilAutoPause: 300 | |
DBSubnetGroupName: | |
Ref: DBSubnetGroup | |
Outputs: | |
PublicEndpoint: | |
Description: That is the LB endpoint | |
Value: !GetAtt LoadBalancer.DNSName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment