├── .gitignore ├── CHANGELOG.md ├── README.md ├── cluster ├── cluster-ec2-private-vpc.yml ├── cluster-ec2-public-vpc.yml ├── cluster-fargate-private-vpc.yml └── cluster-fargate-public-vpc.yml ├── images ├── private-subnet-private-lb.png ├── private-subnet-private-service-discovery.png ├── private-subnet-public-lb.png └── public-subnet-public-lb.png ├── ingress ├── alb-external.yml ├── alb-internal.yml └── service-discovery-internal.yml └── service ├── service-ec2-private-discovery.yml ├── service-ec2-private-lb.yml ├── service-ec2-public-lb.yml ├── service-fargate-private-subnet-private-discovery.yml ├── service-fargate-private-subnet-private-lb.yml ├── service-fargate-private-subnet-public-lb.yml └── service-fargate-public-subnet-public-lb.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## August 9th, 2018 2 | 3 | - Fixed mismatched color in the diagram image for private subnet, private LB 4 | - Fixed bug with export namespacing in Fargate clusters 5 | - Found and fixed a few more !Join functions 6 | - Added an example of private, internal service discovery 7 | - Reorganizing the README.md a little bit 8 | 9 | ## August 7th, 2018 10 | 11 | - Broke templates up so that there is a cluster template, ingress template, and service template instead of a combined cluster + ingress template. 12 | - Updated the list of allowed instance types in the EC2 cluster template to the latest generation instances of each type. 13 | - Added a CloudWatch logs integration so that logs from containers are automatically sent to CloudWatch 14 | - Added CPU based autoscaling rules to automatically scale the number of containers 15 | - Refactoring all `!Join` to `!Sub` for more readable CloudFormation 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deploy containers using Elastic Container Service and CloudFormation 2 | 3 | __Note: I have created an updated and modernized version of these patterns, accessible at [Containers on AWS patterns for CloudFormation](https://containersonaws.com/pattern/?tool=cloudformation). The new collection of patterns is much larger, has a lot more ECS feature coverage, and has filters that help you explore and find an applicable example more easily.__ 4 | 5 | ECS and Fargate give you a lot of control over how you want to deploy containers, and how you would like them to be networked and accessed. This repository contains CloudFormation templates to help you setup several common architectures across both AWS ECS on EC2 and AWS ECS on AWS Fargate. 6 | 7 | To get started use the AWS CLI to execute the following command. This will create a role that enables ECS on your account, so the following reference templates will work properly: 8 | 9 | ``` 10 | aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com 11 | ``` 12 | 13 | Next choose one of the following common architecture stacks which many customers use. The templates can be deployed either using the AWS CLI, or in the AWS CloudFormation console. 14 | 15 |   16 | 17 |   18 | 19 | ### Publically networked service, with public load balancer 20 | 21 | This is a service with direct access to the internet, and exposed publically to the internet behind a public load balancer so that people can access it. The service has a public IP address so it can initiate direct communication to other things on the internet. This approach is one of the most simple for public facing services. 22 | 23 | ![public subnet public lb](images/public-subnet-public-lb.png) 24 | __EC2 hosted:__ 25 | 26 | 1. Deploy the [EC2 Cluster with fully public network](cluster/cluster-ec2-public-vpc.yml) 27 | 2. Deploy the [external, public ALB](ingress/alb-external.yml) ingress 28 | 3. Deploy the [public load balanced EC2 service template](service/service-ec2-public-lb.yml) 29 | 30 | __Fargate hosted:__ 31 | 32 | 1. Deploy the [Fargate cluster with public subnet](cluster/cluster-fargate-public-vpc.yml) 33 | 2. Deploy the [external, public ALB](ingress/alb-external.yml) ingress 34 | 3. Deploy the [public load balanced Fargate service template](service/service-fargate-public-subnet-public-lb.yml) 35 | 36 |   37 | 38 |   39 | 40 | ### Privately networked service, with public load balancer 41 | 42 | This is a service protected inside a private subnet, with no direct internet access. Because the service does not have public IP address it must initiate outbound connections through a NAT Gateway, which communicates to the external internet on the service's behalf. However, you may still want to give the public limited access to the service via a load balancer which is public. 43 | 44 | ![private subnet public lb](images/private-subnet-public-lb.png) 45 | __EC2 hosted:__ 46 | 47 | 1. Deploy the [EC2 Cluster with fully private network](cluster/cluster-ec2-private-vpc.yml) 48 | 2. Deploy the [external, public ALB](ingress/alb-external.yml) ingress 49 | 3. Deploy the [public load balanced EC2 service template](service/service-ec2-public-lb.yml) 50 | 51 | __Fargate hosted:__ 52 | 53 | 1. Deploy the [Fargate cluster with public subnet](cluster/cluster-fargate-private-vpc.yml) 54 | 2. Deploy the [external, public ALB](ingress/alb-external.yml) ingress 55 | 3. Deploy the [private subnet, public load balanced Fargate service template](service/service-fargate-private-subnet-public-lb.yml) 56 | 57 |   58 | 59 |   60 | 61 | ### Privately networked service, with private load balancer 62 | 63 | This is a service which is protected inside a private subnet. Not only does it not have a public IP address, but it also behind a private load balancer which can only be accessed by your own services. This is often used for internal services, where one frontend service communicates to a backend service which the public is not intended to directly access. In the diagram below notice how someone from the public internet initiates the blue connection to the public facing service in the public subnet, but that service can then initiate a green connection the private internal service: 64 | 65 | ![private subnet private lb](images/private-subnet-private-lb.png) 66 | __EC2 hosted:__ 67 | 68 | 1. Deploy the [EC2 Cluster with fully private network](cluster/cluster-ec2-private-vpc.yml) 69 | 2. Deploy the [internal, private ALB](ingress/alb-internal.yml) ingress 70 | 3. Deploy the [public load balanced EC2 service template](service/service-ec2-public-lb.yml) 71 | 72 | __Fargate hosted:__ 73 | 74 | 1. Deploy the [Fargate cluster with private subnet](cluster/cluster-fargate-private-vpc.yml) 75 | 2. Deploy the [internal, private ALB](ingress/alb-internal.yml) ingress 76 | 3. Deploy the [private subnet, private load balanced Fargate service template](service/service-fargate-private-subnet-private-lb.yml) 77 | 78 |   79 | 80 |   81 | 82 | ### Privately networked service, with service discovery 83 | 84 | This service type is privately networked, so it only has a private IP address, and can't receive any traffic directly from the internet. Rather than using a load balancer, this service uses a service discovery mechanism to register's its private IP address for others to discover. Another service or user can use DNS based discovery or API based discovery to get the direct IP address of the container and talk directly to it. This approach is fantastic for internal communications between private services in the same private tier of your application. 85 | 86 | ![private subnet private service discovery](images/private-subnet-private-service-discovery.png) 87 | __EC2 hosted:__ 88 | 89 | _While this combination is currently possible via the API and console, it is not included here because it is pending full CloudFormation support for `bridge` mode networking combined with service discovery. Check [this example template](service/service-ec2-private-discovery.yml) to see how it will look once support is added._ 90 | 91 | __Fargate hosted:__ 92 | 93 | 1. Deploy the [Fargate cluster with private subnet](cluster/cluster-fargate-private-vpc.yml) 94 | 2. Deploy the [internal service discovery](ingress/service-discovery-internal.yml) ingress 95 | 3. Deploy the [private subnet, private service discovery Fargate service template](service/service-fargate-private-subnet-private-discovery.yml) 96 | 97 |   98 | 99 |   100 | 101 | ## Your ingress address 102 | 103 | __ALB Ingress:__ 104 | 105 | Once the service stack is deployed check the outputs tab of the ingress stack that you deployed to get the URL to use to access your containers. Note that an external ALB's URL will be accessible to the public from any computer, but an internal ALB's URL will only be accessible if you make the request from an instance inside the VPC, with the appropriate security group. If you want to test spin up a Cloud 9 development environment in the VPC, add its security group to the load balancer's security group and execute a `curl` command. 106 | 107 | __Service Discovery Ingress:__ 108 | 109 | For service discovery things are a little different. The service is available at `.` where `domain` is the domain address you entered when you created the service discovery ingress. Once again if you are using a private, internal servicex discovery endpoint the DNS name will only resolve for hosts inside the VPC. You can test this by adding a Cloud 9 development environment to the VPC and executing a command like: 110 | 111 | ``` 112 | dig +short nginx.service.production 113 | ``` 114 | 115 |   116 | 117 |   118 | 119 | ## Further customizations 120 | 121 | Note that these baseline templates have only HTTP listeners (no SSL support) but this can be easily added to the templates once you create or import an SSL certificate into Amazon Certificate Manager. Additionally, you may want to customize the default autoscaling rules that are embedded in the service template. 122 | -------------------------------------------------------------------------------- /cluster/cluster-ec2-private-vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: EC2 ECS cluster running containers in a private subnet. Supports 3 | public facing load balancers, private internal load balancers, and 4 | both internal and external service discovery namespaces. 5 | Parameters: 6 | EnvironmentName: 7 | Type: String 8 | Default: production 9 | Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" 10 | InstanceType: 11 | Description: EC2 instance type 12 | Type: String 13 | Default: c5.xlarge 14 | Description: Class of EC2 instance used to host containers. Choose t2 for testing, m5 for general purpose, c5 for CPU intensive services, and r5 for memory intensive services 15 | AllowedValues: [ t2.micro, t2.small, t2.medium, t2.large, t2.xlarge, t2.2xlarge, 16 | m5.large, m5.xlarge, m5.2large, m5.4xlarge, m5.12xlarge, m5.24large, 17 | c5.large, c5.xlarge, c5.2xlarge, c5.4xlarge, c5.9xlarge, c5.18xlarge, 18 | r5.large, r5.xlarge, r5.2xlarge, r5.4xlarge, r5.12xlarge, r5.24xlarge ] 19 | ConstraintDescription: Please choose a valid instance type. 20 | DesiredCapacity: 21 | Type: Number 22 | Default: '3' 23 | Description: Number of EC2 instances to launch in your ECS cluster. 24 | MaxSize: 25 | Type: Number 26 | Default: '6' 27 | Description: Maximum number of EC2 instances that can be launched in your ECS cluster. 28 | ECSAMI: 29 | Description: AMI ID 30 | Type: AWS::SSM::Parameter::Value 31 | Default: /aws/service/ecs/optimized-ami/amazon-linux/recommended/image_id 32 | Description: The Amazon Machine Image ID used for the cluster, leave it as the default value to get the latest AMI 33 | 34 | Mappings: 35 | # Hard values for the subnet masks. These masks define 36 | # the range of internal IP addresses that can be assigned. 37 | # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 38 | # There are four subnets which cover the ranges: 39 | # 40 | # 10.0.0.0 - 10.0.0.255 41 | # 10.0.1.0 - 10.0.1.255 42 | # 10.0.2.0 - 10.0.2.255 43 | # 10.0.3.0 - 10.0.3.255 44 | # 45 | # If you need more IP addresses (perhaps you have so many 46 | # instances that you run out) then you can customize these 47 | # ranges to add more 48 | SubnetConfig: 49 | VPC: 50 | CIDR: '10.0.0.0/16' 51 | PublicOne: 52 | CIDR: '10.0.0.0/24' 53 | PublicTwo: 54 | CIDR: '10.0.1.0/24' 55 | PrivateOne: 56 | CIDR: '10.0.2.0/24' 57 | PrivateTwo: 58 | CIDR: '10.0.3.0/24' 59 | Resources: 60 | # VPC in which containers will be networked. 61 | # It has two public subnets, and two private subnets. 62 | # We distribute the subnets across the first two available subnets 63 | # for the region, for high availability. 64 | VPC: 65 | Type: AWS::EC2::VPC 66 | Properties: 67 | EnableDnsSupport: true 68 | EnableDnsHostnames: true 69 | CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 70 | 71 | # Two public subnets, where containers can have public IP addresses 72 | PublicSubnetOne: 73 | Type: AWS::EC2::Subnet 74 | Properties: 75 | AvailabilityZone: 76 | Fn::Select: 77 | - 0 78 | - Fn::GetAZs: {Ref: 'AWS::Region'} 79 | VpcId: !Ref 'VPC' 80 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 81 | MapPublicIpOnLaunch: true 82 | PublicSubnetTwo: 83 | Type: AWS::EC2::Subnet 84 | Properties: 85 | AvailabilityZone: 86 | Fn::Select: 87 | - 1 88 | - Fn::GetAZs: {Ref: 'AWS::Region'} 89 | VpcId: !Ref 'VPC' 90 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 91 | MapPublicIpOnLaunch: true 92 | 93 | # Two private subnets where containers will only have private 94 | # IP addresses, and will only be reachable by other members of the 95 | # VPC 96 | PrivateSubnetOne: 97 | Type: AWS::EC2::Subnet 98 | Properties: 99 | AvailabilityZone: 100 | Fn::Select: 101 | - 0 102 | - Fn::GetAZs: {Ref: 'AWS::Region'} 103 | VpcId: !Ref 'VPC' 104 | CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR'] 105 | PrivateSubnetTwo: 106 | Type: AWS::EC2::Subnet 107 | Properties: 108 | AvailabilityZone: 109 | Fn::Select: 110 | - 1 111 | - Fn::GetAZs: {Ref: 'AWS::Region'} 112 | VpcId: !Ref 'VPC' 113 | CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR'] 114 | 115 | # Setup networking resources for the public subnets. Containers 116 | # in the public subnets have public IP addresses and the routing table 117 | # sends network traffic via the internet gateway. 118 | InternetGateway: 119 | Type: AWS::EC2::InternetGateway 120 | GatewayAttachement: 121 | Type: AWS::EC2::VPCGatewayAttachment 122 | Properties: 123 | VpcId: !Ref 'VPC' 124 | InternetGatewayId: !Ref 'InternetGateway' 125 | PublicRouteTable: 126 | Type: AWS::EC2::RouteTable 127 | Properties: 128 | VpcId: !Ref 'VPC' 129 | PublicRoute: 130 | Type: AWS::EC2::Route 131 | DependsOn: GatewayAttachement 132 | Properties: 133 | RouteTableId: !Ref 'PublicRouteTable' 134 | DestinationCidrBlock: '0.0.0.0/0' 135 | GatewayId: !Ref 'InternetGateway' 136 | PublicSubnetOneRouteTableAssociation: 137 | Type: AWS::EC2::SubnetRouteTableAssociation 138 | Properties: 139 | SubnetId: !Ref PublicSubnetOne 140 | RouteTableId: !Ref PublicRouteTable 141 | PublicSubnetTwoRouteTableAssociation: 142 | Type: AWS::EC2::SubnetRouteTableAssociation 143 | Properties: 144 | SubnetId: !Ref PublicSubnetTwo 145 | RouteTableId: !Ref PublicRouteTable 146 | 147 | # Setup networking resources for the private subnets. Containers 148 | # in these subnets have only private IP addresses, and must use a NAT 149 | # gateway to talk to the internet. We launch two NAT gateways, one for 150 | # each private subnet. 151 | NatGatewayOneAttachment: 152 | Type: AWS::EC2::EIP 153 | DependsOn: GatewayAttachement 154 | Properties: 155 | Domain: vpc 156 | NatGatewayTwoAttachment: 157 | Type: AWS::EC2::EIP 158 | DependsOn: GatewayAttachement 159 | Properties: 160 | Domain: vpc 161 | NatGatewayOne: 162 | Type: AWS::EC2::NatGateway 163 | Properties: 164 | AllocationId: !GetAtt NatGatewayOneAttachment.AllocationId 165 | SubnetId: !Ref PublicSubnetOne 166 | NatGatewayTwo: 167 | Type: AWS::EC2::NatGateway 168 | Properties: 169 | AllocationId: !GetAtt NatGatewayTwoAttachment.AllocationId 170 | SubnetId: !Ref PublicSubnetTwo 171 | PrivateRouteTableOne: 172 | Type: AWS::EC2::RouteTable 173 | Properties: 174 | VpcId: !Ref 'VPC' 175 | PrivateRouteOne: 176 | Type: AWS::EC2::Route 177 | Properties: 178 | RouteTableId: !Ref PrivateRouteTableOne 179 | DestinationCidrBlock: 0.0.0.0/0 180 | NatGatewayId: !Ref NatGatewayOne 181 | PrivateRouteTableOneAssociation: 182 | Type: AWS::EC2::SubnetRouteTableAssociation 183 | Properties: 184 | RouteTableId: !Ref PrivateRouteTableOne 185 | SubnetId: !Ref PrivateSubnetOne 186 | PrivateRouteTableTwo: 187 | Type: AWS::EC2::RouteTable 188 | Properties: 189 | VpcId: !Ref 'VPC' 190 | PrivateRouteTwo: 191 | Type: AWS::EC2::Route 192 | Properties: 193 | RouteTableId: !Ref PrivateRouteTableTwo 194 | DestinationCidrBlock: 0.0.0.0/0 195 | NatGatewayId: !Ref NatGatewayTwo 196 | PrivateRouteTableTwoAssociation: 197 | Type: AWS::EC2::SubnetRouteTableAssociation 198 | Properties: 199 | RouteTableId: !Ref PrivateRouteTableTwo 200 | SubnetId: !Ref PrivateSubnetTwo 201 | 202 | # OPTIONAL: VPC Endpoint for DynamoDB 203 | # If a container needs to access DynamoDB this allows a container in the private subnet 204 | # to talk to DynamoDB directly without needing to go via the NAT gateway. This reduces 205 | # the amount of bandwidth through the gateway, meaning that the gateway is free to serve 206 | # your other traffic. 207 | DynamoDBEndpoint: 208 | Type: AWS::EC2::VPCEndpoint 209 | Properties: 210 | PolicyDocument: 211 | Version: "2012-10-17" 212 | Statement: 213 | - Effect: Allow 214 | Action: "*" 215 | Principal: "*" 216 | Resource: "*" 217 | RouteTableIds: 218 | - !Ref 'PrivateRouteTableOne' 219 | - !Ref 'PrivateRouteTableTwo' 220 | ServiceName: !Sub com.amazonaws.${AWS::Region}.dynamodb 221 | VpcId: !Ref 'VPC' 222 | 223 | # ECS Resources 224 | ECSCluster: 225 | Type: AWS::ECS::Cluster 226 | Properties: 227 | ClusterName: !Ref EnvironmentName 228 | 229 | # A security group for the EC2 hosts that will run the containers. 230 | # Rules are added based on what ingress you choose to add to the cluster. 231 | ContainerSecurityGroup: 232 | Type: AWS::EC2::SecurityGroup 233 | Properties: 234 | GroupDescription: Access to the ECS hosts that run containers 235 | VpcId: !Ref 'VPC' 236 | 237 | # Autoscaling group. This launches the actual EC2 instances that will register 238 | # themselves as members of the cluster, and run the docker containers. 239 | ECSAutoScalingGroup: 240 | Type: AWS::AutoScaling::AutoScalingGroup 241 | Properties: 242 | VPCZoneIdentifier: 243 | - !Ref PrivateSubnetOne 244 | - !Ref PrivateSubnetTwo 245 | LaunchConfigurationName: !Ref 'ContainerInstances' 246 | MinSize: '1' 247 | MaxSize: !Ref 'MaxSize' 248 | DesiredCapacity: !Ref 'DesiredCapacity' 249 | CreationPolicy: 250 | ResourceSignal: 251 | Timeout: PT15M 252 | UpdatePolicy: 253 | AutoScalingReplacingUpdate: 254 | WillReplace: 'true' 255 | ContainerInstances: 256 | Type: AWS::AutoScaling::LaunchConfiguration 257 | Properties: 258 | ImageId: !Ref 'ECSAMI' 259 | SecurityGroups: [!Ref 'ContainerSecurityGroup'] 260 | InstanceType: !Ref 'InstanceType' 261 | IamInstanceProfile: !Ref 'EC2InstanceProfile' 262 | UserData: 263 | Fn::Base64: !Sub | 264 | #!/bin/bash -xe 265 | echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config 266 | yum install -y aws-cfn-bootstrap 267 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} 268 | EC2InstanceProfile: 269 | Type: AWS::IAM::InstanceProfile 270 | Properties: 271 | Path: / 272 | Roles: [!Ref 'EC2Role'] 273 | 274 | # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets 275 | # on your AWS account 276 | AutoscalingRole: 277 | Type: AWS::IAM::Role 278 | Properties: 279 | AssumeRolePolicyDocument: 280 | Statement: 281 | - Effect: Allow 282 | Principal: 283 | Service: [application-autoscaling.amazonaws.com] 284 | Action: ['sts:AssumeRole'] 285 | Path: / 286 | Policies: 287 | - PolicyName: service-autoscaling 288 | PolicyDocument: 289 | Statement: 290 | - Effect: Allow 291 | Action: 292 | - 'application-autoscaling:*' 293 | - 'cloudwatch:DescribeAlarms' 294 | - 'cloudwatch:PutMetricAlarm' 295 | - 'ecs:DescribeServices' 296 | - 'ecs:UpdateService' 297 | Resource: '*' 298 | 299 | # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts 300 | # to communciate with the ECS control plane, as well as download the docker 301 | # images from ECR to run on your host. 302 | EC2Role: 303 | Type: AWS::IAM::Role 304 | Properties: 305 | AssumeRolePolicyDocument: 306 | Statement: 307 | - Effect: Allow 308 | Principal: 309 | Service: [ec2.amazonaws.com] 310 | Action: ['sts:AssumeRole'] 311 | Path: / 312 | Policies: 313 | - PolicyName: ecs-service 314 | PolicyDocument: 315 | Statement: 316 | - Effect: Allow 317 | Action: 318 | - 'ecs:CreateCluster' 319 | - 'ecs:DeregisterContainerInstance' 320 | - 'ecs:DiscoverPollEndpoint' 321 | - 'ecs:Poll' 322 | - 'ecs:RegisterContainerInstance' 323 | - 'ecs:StartTelemetrySession' 324 | - 'ecs:Submit*' 325 | - 'logs:CreateLogStream' 326 | - 'logs:PutLogEvents' 327 | - 'ecr:GetAuthorizationToken' 328 | - 'ecr:BatchGetImage' 329 | - 'ecr:GetDownloadUrlForLayer' 330 | Resource: '*' 331 | 332 | # This is an IAM role which authorizes ECS to manage resources on your 333 | # account on your behalf, such as updating your load balancer with the 334 | # details of where your containers are, so that traffic can reach your 335 | # containers. 336 | ECSRole: 337 | Type: AWS::IAM::Role 338 | Properties: 339 | AssumeRolePolicyDocument: 340 | Statement: 341 | - Effect: Allow 342 | Principal: 343 | Service: [ecs.amazonaws.com] 344 | Action: ['sts:AssumeRole'] 345 | Path: / 346 | Policies: 347 | - PolicyName: ecs-service 348 | PolicyDocument: 349 | Statement: 350 | - Effect: Allow 351 | Action: 352 | # Rules which allow ECS to attach network interfaces to instances 353 | # on your behalf in order for awsvpc networking mode to work right 354 | - 'ec2:AttachNetworkInterface' 355 | - 'ec2:CreateNetworkInterface' 356 | - 'ec2:CreateNetworkInterfacePermission' 357 | - 'ec2:DeleteNetworkInterface' 358 | - 'ec2:DeleteNetworkInterfacePermission' 359 | - 'ec2:Describe*' 360 | - 'ec2:DetachNetworkInterface' 361 | 362 | # Rules which allow ECS to update load balancers on your behalf 363 | # with the information sabout how to send traffic to your containers 364 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 365 | - 'elasticloadbalancing:DeregisterTargets' 366 | - 'elasticloadbalancing:Describe*' 367 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 368 | - 'elasticloadbalancing:RegisterTargets' 369 | Resource: '*' 370 | 371 | # These are the values output by the CloudFormation template. Be careful 372 | # about changing any of them, because of them are exported with specific 373 | # names so that the other task related CF templates can use them. 374 | Outputs: 375 | ClusterName: 376 | Description: The name of the ECS cluster 377 | Value: !Ref 'ECSCluster' 378 | Export: 379 | Name: !Sub ${EnvironmentName}:ClusterName 380 | AutoscalingRole: 381 | Description: The ARN of the role used for autoscaling 382 | Value: !GetAtt 'AutoscalingRole.Arn' 383 | Export: 384 | Name: !Sub ${EnvironmentName}:AutoscalingRole 385 | ECSRole: 386 | Description: The ARN of the ECS role 387 | Value: !GetAtt 'ECSRole.Arn' 388 | Export: 389 | Name: !Sub ${EnvironmentName}:ECSRole 390 | VpcId: 391 | Description: The ID of the VPC that this stack is deployed in 392 | Value: !Ref 'VPC' 393 | Export: 394 | Name: !Sub ${EnvironmentName}:VpcId 395 | PublicSubnetOne: 396 | Description: Public subnet one 397 | Value: !Ref 'PublicSubnetOne' 398 | Export: 399 | Name: !Sub ${EnvironmentName}:PublicSubnetOne 400 | PublicSubnetTwo: 401 | Description: Public subnet two 402 | Value: !Ref 'PublicSubnetTwo' 403 | Export: 404 | Name: !Sub ${EnvironmentName}:PublicSubnetTwo 405 | PrivateSubnetOne: 406 | Description: Private subnet one 407 | Value: !Ref 'PrivateSubnetOne' 408 | Export: 409 | Name: !Sub ${EnvironmentName}:PrivateSubnetOne 410 | PrivateSubnetTwo: 411 | Description: Private subnet two 412 | Value: !Ref 'PrivateSubnetTwo' 413 | Export: 414 | Name: !Sub ${EnvironmentName}:PrivateSubnetTwo 415 | ContainerSecurityGroup: 416 | Description: A security group used to allow containers to receive traffic 417 | Value: !Ref 'ContainerSecurityGroup' 418 | Export: 419 | Name: !Sub ${EnvironmentName}:ContainerSecurityGroup 420 | -------------------------------------------------------------------------------- /cluster/cluster-ec2-public-vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: EC2 ECS cluster running containers in a public subnet. Only supports 3 | public facing load balancer, and public service discovery namespaces. 4 | Parameters: 5 | EnvironmentName: 6 | Type: String 7 | Default: production 8 | Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" 9 | InstanceType: 10 | Description: EC2 instance type 11 | Type: String 12 | Default: c5.xlarge 13 | Description: Class of EC2 instance used to host containers. Choose t2 for testing, m5 for general purpose, c5 for CPU intensive services, and r5 for memory intensive services 14 | AllowedValues: [ t2.micro, t2.small, t2.medium, t2.large, t2.xlarge, t2.2xlarge, 15 | m5.large, m5.xlarge, m5.2large, m5.4xlarge, m5.12xlarge, m5.24large, 16 | c5.large, c5.xlarge, c5.2xlarge, c5.4xlarge, c5.9xlarge, c5.18xlarge, 17 | r5.large, r5.xlarge, r5.2xlarge, r5.4xlarge, r5.12xlarge, r5.24xlarge ] 18 | ConstraintDescription: Please choose a valid instance type. 19 | DesiredCapacity: 20 | Type: Number 21 | Default: '3' 22 | Description: Number of EC2 instances to launch in your ECS cluster. 23 | MaxSize: 24 | Type: Number 25 | Default: '6' 26 | Description: Maximum number of EC2 instances that can be launched in your ECS cluster. 27 | ECSAMI: 28 | Description: AMI ID 29 | Type: AWS::SSM::Parameter::Value 30 | Default: /aws/service/ecs/optimized-ami/amazon-linux/recommended/image_id 31 | Description: The Amazon Machine Image ID used for the cluster, leave it as the default value to get the latest AMI 32 | 33 | Mappings: 34 | # Hard values for the subnet masks. These masks define 35 | # the range of internal IP addresses that can be assigned. 36 | # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 37 | # There are two subnets which cover the ranges: 38 | # 39 | # 10.0.0.0 - 10.0.0.255 40 | # 10.0.1.0 - 10.0.1.255 41 | # 42 | # If you need more IP addresses (perhaps you have so many 43 | # instances that you run out) then you can customize these 44 | # ranges to add more 45 | SubnetConfig: 46 | VPC: 47 | CIDR: '10.0.0.0/16' 48 | PublicOne: 49 | CIDR: '10.0.0.0/24' 50 | PublicTwo: 51 | CIDR: '10.0.1.0/24' 52 | Resources: 53 | # VPC in which containers will be networked. 54 | # It has two public subnets 55 | # We distribute the subnets across the first two available subnets 56 | # for the region, for high availability. 57 | VPC: 58 | Type: AWS::EC2::VPC 59 | Properties: 60 | EnableDnsSupport: true 61 | EnableDnsHostnames: true 62 | CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 63 | 64 | # Two public subnets, where containers can have public IP addresses 65 | PublicSubnetOne: 66 | Type: AWS::EC2::Subnet 67 | Properties: 68 | AvailabilityZone: 69 | Fn::Select: 70 | - 0 71 | - Fn::GetAZs: {Ref: 'AWS::Region'} 72 | VpcId: !Ref 'VPC' 73 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 74 | MapPublicIpOnLaunch: true 75 | PublicSubnetTwo: 76 | Type: AWS::EC2::Subnet 77 | Properties: 78 | AvailabilityZone: 79 | Fn::Select: 80 | - 1 81 | - Fn::GetAZs: {Ref: 'AWS::Region'} 82 | VpcId: !Ref 'VPC' 83 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 84 | MapPublicIpOnLaunch: true 85 | 86 | # Setup networking resources for the public subnets. Containers 87 | # in the public subnets have public IP addresses and the routing table 88 | # sends network traffic via the internet gateway. 89 | InternetGateway: 90 | Type: AWS::EC2::InternetGateway 91 | GatewayAttachement: 92 | Type: AWS::EC2::VPCGatewayAttachment 93 | Properties: 94 | VpcId: !Ref 'VPC' 95 | InternetGatewayId: !Ref 'InternetGateway' 96 | PublicRouteTable: 97 | Type: AWS::EC2::RouteTable 98 | Properties: 99 | VpcId: !Ref 'VPC' 100 | PublicRoute: 101 | Type: AWS::EC2::Route 102 | DependsOn: GatewayAttachement 103 | Properties: 104 | RouteTableId: !Ref 'PublicRouteTable' 105 | DestinationCidrBlock: '0.0.0.0/0' 106 | GatewayId: !Ref 'InternetGateway' 107 | PublicSubnetOneRouteTableAssociation: 108 | Type: AWS::EC2::SubnetRouteTableAssociation 109 | Properties: 110 | SubnetId: !Ref PublicSubnetOne 111 | RouteTableId: !Ref PublicRouteTable 112 | PublicSubnetTwoRouteTableAssociation: 113 | Type: AWS::EC2::SubnetRouteTableAssociation 114 | Properties: 115 | SubnetId: !Ref PublicSubnetTwo 116 | RouteTableId: !Ref PublicRouteTable 117 | 118 | # ECS Resources 119 | ECSCluster: 120 | Type: AWS::ECS::Cluster 121 | 122 | # A security group for the EC2 hosts that will run the containers. 123 | # Rules will be added depending on what ingress is created. 124 | ContainerSecurityGroup: 125 | Type: AWS::EC2::SecurityGroup 126 | Properties: 127 | GroupDescription: Access to the ECS hosts that run containers 128 | VpcId: !Ref 'VPC' 129 | 130 | # Autoscaling group. This launches the actual EC2 instances that will register 131 | # themselves as members of the cluster, and run the docker containers. 132 | ECSAutoScalingGroup: 133 | Type: AWS::AutoScaling::AutoScalingGroup 134 | Properties: 135 | VPCZoneIdentifier: 136 | - !Ref PublicSubnetOne 137 | - !Ref PublicSubnetTwo 138 | LaunchConfigurationName: !Ref 'ContainerInstances' 139 | MinSize: '1' 140 | MaxSize: !Ref 'MaxSize' 141 | DesiredCapacity: !Ref 'DesiredCapacity' 142 | CreationPolicy: 143 | ResourceSignal: 144 | Timeout: PT15M 145 | UpdatePolicy: 146 | AutoScalingReplacingUpdate: 147 | WillReplace: 'true' 148 | ContainerInstances: 149 | Type: AWS::AutoScaling::LaunchConfiguration 150 | Properties: 151 | ImageId: !Ref 'ECSAMI' 152 | SecurityGroups: [!Ref 'ContainerSecurityGroup'] 153 | InstanceType: !Ref 'InstanceType' 154 | IamInstanceProfile: !Ref 'EC2InstanceProfile' 155 | UserData: 156 | Fn::Base64: !Sub | 157 | #!/bin/bash -xe 158 | echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config 159 | yum install -y aws-cfn-bootstrap 160 | /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} 161 | EC2InstanceProfile: 162 | Type: AWS::IAM::InstanceProfile 163 | Properties: 164 | Path: / 165 | Roles: [!Ref 'EC2Role'] 166 | 167 | # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets 168 | # on your AWS account 169 | AutoscalingRole: 170 | Type: AWS::IAM::Role 171 | Properties: 172 | AssumeRolePolicyDocument: 173 | Statement: 174 | - Effect: Allow 175 | Principal: 176 | Service: [application-autoscaling.amazonaws.com] 177 | Action: ['sts:AssumeRole'] 178 | Path: / 179 | Policies: 180 | - PolicyName: service-autoscaling 181 | PolicyDocument: 182 | Statement: 183 | - Effect: Allow 184 | Action: 185 | - 'application-autoscaling:*' 186 | - 'cloudwatch:DescribeAlarms' 187 | - 'cloudwatch:PutMetricAlarm' 188 | - 'ecs:DescribeServices' 189 | - 'ecs:UpdateService' 190 | Resource: '*' 191 | 192 | # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts 193 | # to communciate with the ECS control plane, as well as download the docker 194 | # images from ECR to run on your host. 195 | EC2Role: 196 | Type: AWS::IAM::Role 197 | Properties: 198 | AssumeRolePolicyDocument: 199 | Statement: 200 | - Effect: Allow 201 | Principal: 202 | Service: [ec2.amazonaws.com] 203 | Action: ['sts:AssumeRole'] 204 | Path: / 205 | Policies: 206 | - PolicyName: ecs-service 207 | PolicyDocument: 208 | Statement: 209 | - Effect: Allow 210 | Action: 211 | - 'ecs:CreateCluster' 212 | - 'ecs:DeregisterContainerInstance' 213 | - 'ecs:DiscoverPollEndpoint' 214 | - 'ecs:Poll' 215 | - 'ecs:RegisterContainerInstance' 216 | - 'ecs:StartTelemetrySession' 217 | - 'ecs:Submit*' 218 | - 'logs:CreateLogStream' 219 | - 'logs:PutLogEvents' 220 | - 'ecr:GetAuthorizationToken' 221 | - 'ecr:BatchGetImage' 222 | - 'ecr:GetDownloadUrlForLayer' 223 | Resource: '*' 224 | 225 | # This is an IAM role which authorizes ECS to manage resources on your 226 | # account on your behalf, such as updating your load balancer with the 227 | # details of where your containers are, so that traffic can reach your 228 | # containers. 229 | ECSRole: 230 | Type: AWS::IAM::Role 231 | Properties: 232 | AssumeRolePolicyDocument: 233 | Statement: 234 | - Effect: Allow 235 | Principal: 236 | Service: [ecs.amazonaws.com] 237 | Action: ['sts:AssumeRole'] 238 | Path: / 239 | Policies: 240 | - PolicyName: ecs-service 241 | PolicyDocument: 242 | Statement: 243 | - Effect: Allow 244 | Action: 245 | # Rules which allow ECS to attach network interfaces to instances 246 | # on your behalf in order for awsvpc networking mode to work right 247 | - 'ec2:AttachNetworkInterface' 248 | - 'ec2:CreateNetworkInterface' 249 | - 'ec2:CreateNetworkInterfacePermission' 250 | - 'ec2:DeleteNetworkInterface' 251 | - 'ec2:DeleteNetworkInterfacePermission' 252 | - 'ec2:Describe*' 253 | - 'ec2:DetachNetworkInterface' 254 | 255 | # Rules which allow ECS to update load balancers on your behalf 256 | # with the information sabout how to send traffic to your containers 257 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 258 | - 'elasticloadbalancing:DeregisterTargets' 259 | - 'elasticloadbalancing:Describe*' 260 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 261 | - 'elasticloadbalancing:RegisterTargets' 262 | Resource: '*' 263 | 264 | # These are the values output by the CloudFormation template. Be careful 265 | # about changing any of them, because of them are exported with specific 266 | # names so that the other task related CF templates can use them. 267 | Outputs: 268 | ClusterName: 269 | Description: The name of the ECS cluster 270 | Value: !Ref 'ECSCluster' 271 | Export: 272 | Name: !Sub ${EnvironmentName}:ClusterName 273 | AutoscalingRole: 274 | Description: The ARN of the role used for autoscaling 275 | Value: !GetAtt 'AutoscalingRole.Arn' 276 | Export: 277 | Name: !Sub ${EnvironmentName}:AutoscalingRole 278 | ECSRole: 279 | Description: The ARN of the ECS role 280 | Value: !GetAtt 'ECSRole.Arn' 281 | Export: 282 | Name: !Sub ${EnvironmentName}:ECSRole 283 | VpcId: 284 | Description: The ID of the VPC that this stack is deployed in 285 | Value: !Ref 'VPC' 286 | Export: 287 | Name: !Sub ${EnvironmentName}:VpcId 288 | PublicSubnetOne: 289 | Description: Public subnet one 290 | Value: !Ref 'PublicSubnetOne' 291 | Export: 292 | Name: !Sub ${EnvironmentName}:PublicSubnetOne 293 | PublicSubnetTwo: 294 | Description: Public subnet two 295 | Value: !Ref 'PublicSubnetTwo' 296 | Export: 297 | Name: !Sub ${EnvironmentName}:PublicSubnetTwo 298 | ContainerSecurityGroup: 299 | Description: A security group used to allow containers to receive traffic 300 | Value: !Ref 'ContainerSecurityGroup' 301 | Export: 302 | Name: !Sub ${EnvironmentName}:ContainerSecurityGroup 303 | -------------------------------------------------------------------------------- /cluster/cluster-fargate-private-vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: AWS Fargate cluster that can span public and private subnets. Supports 3 | public facing load balancers, private internal load balancers, and 4 | both internal and external service discovery namespaces. 5 | Parameters: 6 | EnvironmentName: 7 | Type: String 8 | Default: production 9 | Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" 10 | Mappings: 11 | # Hard values for the subnet masks. These masks define 12 | # the range of internal IP addresses that can be assigned. 13 | # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 14 | # There are four subnets which cover the ranges: 15 | # 16 | # 10.0.0.0 - 10.0.0.255 17 | # 10.0.1.0 - 10.0.1.255 18 | # 10.0.2.0 - 10.0.2.255 19 | # 10.0.3.0 - 10.0.3.255 20 | # 21 | # If you need more IP addresses (perhaps you have so many 22 | # instances that you run out) then you can customize these 23 | # ranges to add more 24 | SubnetConfig: 25 | VPC: 26 | CIDR: '10.0.0.0/16' 27 | PublicOne: 28 | CIDR: '10.0.0.0/24' 29 | PublicTwo: 30 | CIDR: '10.0.1.0/24' 31 | PrivateOne: 32 | CIDR: '10.0.2.0/24' 33 | PrivateTwo: 34 | CIDR: '10.0.3.0/24' 35 | Resources: 36 | # VPC in which containers will be networked. 37 | # It has two public subnets, and two private subnets. 38 | # We distribute the subnets across the first two available subnets 39 | # for the region, for high availability. 40 | VPC: 41 | Type: AWS::EC2::VPC 42 | Properties: 43 | EnableDnsSupport: true 44 | EnableDnsHostnames: true 45 | CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 46 | 47 | # Two public subnets, where containers can have public IP addresses 48 | PublicSubnetOne: 49 | Type: AWS::EC2::Subnet 50 | Properties: 51 | AvailabilityZone: !Select 52 | - 0 53 | - Fn::GetAZs: !Ref 'AWS::Region' 54 | VpcId: !Ref 'VPC' 55 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 56 | MapPublicIpOnLaunch: true 57 | PublicSubnetTwo: 58 | Type: AWS::EC2::Subnet 59 | Properties: 60 | AvailabilityZone: !Select 61 | - 1 62 | - Fn::GetAZs: !Ref 'AWS::Region' 63 | VpcId: !Ref 'VPC' 64 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 65 | MapPublicIpOnLaunch: true 66 | 67 | # Two private subnets where containers will only have private 68 | # IP addresses, and will only be reachable by other members of the 69 | # VPC 70 | PrivateSubnetOne: 71 | Type: AWS::EC2::Subnet 72 | Properties: 73 | AvailabilityZone: !Select 74 | - 0 75 | - Fn::GetAZs: !Ref 'AWS::Region' 76 | VpcId: !Ref 'VPC' 77 | CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR'] 78 | PrivateSubnetTwo: 79 | Type: AWS::EC2::Subnet 80 | Properties: 81 | AvailabilityZone: !Select 82 | - 1 83 | - Fn::GetAZs: !Ref 'AWS::Region' 84 | VpcId: !Ref 'VPC' 85 | CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR'] 86 | 87 | # Setup networking resources for the public subnets. Containers 88 | # in the public subnets have public IP addresses and the routing table 89 | # sends network traffic via the internet gateway. 90 | InternetGateway: 91 | Type: AWS::EC2::InternetGateway 92 | GatewayAttachement: 93 | Type: AWS::EC2::VPCGatewayAttachment 94 | Properties: 95 | VpcId: !Ref 'VPC' 96 | InternetGatewayId: !Ref 'InternetGateway' 97 | PublicRouteTable: 98 | Type: AWS::EC2::RouteTable 99 | Properties: 100 | VpcId: !Ref 'VPC' 101 | PublicRoute: 102 | Type: AWS::EC2::Route 103 | DependsOn: GatewayAttachement 104 | Properties: 105 | RouteTableId: !Ref 'PublicRouteTable' 106 | DestinationCidrBlock: '0.0.0.0/0' 107 | GatewayId: !Ref 'InternetGateway' 108 | PublicSubnetOneRouteTableAssociation: 109 | Type: AWS::EC2::SubnetRouteTableAssociation 110 | Properties: 111 | SubnetId: !Ref PublicSubnetOne 112 | RouteTableId: !Ref PublicRouteTable 113 | PublicSubnetTwoRouteTableAssociation: 114 | Type: AWS::EC2::SubnetRouteTableAssociation 115 | Properties: 116 | SubnetId: !Ref PublicSubnetTwo 117 | RouteTableId: !Ref PublicRouteTable 118 | 119 | # Setup networking resources for the private subnets. Containers 120 | # in these subnets have only private IP addresses, and must use a NAT 121 | # gateway to talk to the internet. We launch two NAT gateways, one for 122 | # each private subnet. 123 | NatGatewayOneAttachment: 124 | Type: AWS::EC2::EIP 125 | DependsOn: GatewayAttachement 126 | Properties: 127 | Domain: vpc 128 | NatGatewayTwoAttachment: 129 | Type: AWS::EC2::EIP 130 | DependsOn: GatewayAttachement 131 | Properties: 132 | Domain: vpc 133 | NatGatewayOne: 134 | Type: AWS::EC2::NatGateway 135 | Properties: 136 | AllocationId: !GetAtt NatGatewayOneAttachment.AllocationId 137 | SubnetId: !Ref PublicSubnetOne 138 | NatGatewayTwo: 139 | Type: AWS::EC2::NatGateway 140 | Properties: 141 | AllocationId: !GetAtt NatGatewayTwoAttachment.AllocationId 142 | SubnetId: !Ref PublicSubnetTwo 143 | PrivateRouteTableOne: 144 | Type: AWS::EC2::RouteTable 145 | Properties: 146 | VpcId: !Ref 'VPC' 147 | PrivateRouteOne: 148 | Type: AWS::EC2::Route 149 | Properties: 150 | RouteTableId: !Ref PrivateRouteTableOne 151 | DestinationCidrBlock: 0.0.0.0/0 152 | NatGatewayId: !Ref NatGatewayOne 153 | PrivateRouteTableOneAssociation: 154 | Type: AWS::EC2::SubnetRouteTableAssociation 155 | Properties: 156 | RouteTableId: !Ref PrivateRouteTableOne 157 | SubnetId: !Ref PrivateSubnetOne 158 | PrivateRouteTableTwo: 159 | Type: AWS::EC2::RouteTable 160 | Properties: 161 | VpcId: !Ref 'VPC' 162 | PrivateRouteTwo: 163 | Type: AWS::EC2::Route 164 | Properties: 165 | RouteTableId: !Ref PrivateRouteTableTwo 166 | DestinationCidrBlock: 0.0.0.0/0 167 | NatGatewayId: !Ref NatGatewayTwo 168 | PrivateRouteTableTwoAssociation: 169 | Type: AWS::EC2::SubnetRouteTableAssociation 170 | Properties: 171 | RouteTableId: !Ref PrivateRouteTableTwo 172 | SubnetId: !Ref PrivateSubnetTwo 173 | 174 | # OPTIONAL: VPC Endpoint for DynamoDB 175 | # If a container needs to access DynamoDB this allows a container in the private subnet 176 | # to talk to DynamoDB directly without needing to go via the NAT gateway. This reduces 177 | # the amount of bandwidth through the gateway, meaning that the gateway is free to serve 178 | # your other traffic. 179 | DynamoDBEndpoint: 180 | Type: AWS::EC2::VPCEndpoint 181 | Properties: 182 | PolicyDocument: 183 | Version: "2012-10-17" 184 | Statement: 185 | - Effect: Allow 186 | Action: "*" 187 | Principal: "*" 188 | Resource: "*" 189 | RouteTableIds: 190 | - !Ref 'PrivateRouteTableOne' 191 | - !Ref 'PrivateRouteTableTwo' 192 | ServiceName: !Sub com.amazonaws.${AWS::Region}.dynamodb 193 | VpcId: !Ref 'VPC' 194 | 195 | # ECS Resources 196 | ECSCluster: 197 | Type: AWS::ECS::Cluster 198 | 199 | # A security group for the containers we will run in Fargate. 200 | # Rules are added to this security group based on what ingress you 201 | # add for the cluster. 202 | ContainerSecurityGroup: 203 | Type: AWS::EC2::SecurityGroup 204 | Properties: 205 | GroupDescription: Access to the Fargate containers 206 | VpcId: !Ref 'VPC' 207 | 208 | # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets 209 | # on your AWS account 210 | AutoscalingRole: 211 | Type: AWS::IAM::Role 212 | Properties: 213 | AssumeRolePolicyDocument: 214 | Statement: 215 | - Effect: Allow 216 | Principal: 217 | Service: [application-autoscaling.amazonaws.com] 218 | Action: ['sts:AssumeRole'] 219 | Path: / 220 | Policies: 221 | - PolicyName: service-autoscaling 222 | PolicyDocument: 223 | Statement: 224 | - Effect: Allow 225 | Action: 226 | - 'application-autoscaling:*' 227 | - 'cloudwatch:DescribeAlarms' 228 | - 'cloudwatch:PutMetricAlarm' 229 | - 'ecs:DescribeServices' 230 | - 'ecs:UpdateService' 231 | Resource: '*' 232 | 233 | # This is an IAM role which authorizes ECS to manage resources on your 234 | # account on your behalf, such as updating your load balancer with the 235 | # details of where your containers are, so that traffic can reach your 236 | # containers. 237 | ECSRole: 238 | Type: AWS::IAM::Role 239 | Properties: 240 | AssumeRolePolicyDocument: 241 | Statement: 242 | - Effect: Allow 243 | Principal: 244 | Service: [ecs.amazonaws.com] 245 | Action: ['sts:AssumeRole'] 246 | Path: / 247 | Policies: 248 | - PolicyName: ecs-service 249 | PolicyDocument: 250 | Statement: 251 | - Effect: Allow 252 | Action: 253 | # Rules which allow ECS to attach network interfaces to instances 254 | # on your behalf in order for awsvpc networking mode to work right 255 | - 'ec2:AttachNetworkInterface' 256 | - 'ec2:CreateNetworkInterface' 257 | - 'ec2:CreateNetworkInterfacePermission' 258 | - 'ec2:DeleteNetworkInterface' 259 | - 'ec2:DeleteNetworkInterfacePermission' 260 | - 'ec2:Describe*' 261 | - 'ec2:DetachNetworkInterface' 262 | 263 | # Rules which allow ECS to update load balancers on your behalf 264 | # with the information sabout how to send traffic to your containers 265 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 266 | - 'elasticloadbalancing:DeregisterTargets' 267 | - 'elasticloadbalancing:Describe*' 268 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 269 | - 'elasticloadbalancing:RegisterTargets' 270 | Resource: '*' 271 | 272 | # This is a role which is used by the ECS tasks themselves. 273 | ECSTaskExecutionRole: 274 | Type: AWS::IAM::Role 275 | Properties: 276 | AssumeRolePolicyDocument: 277 | Statement: 278 | - Effect: Allow 279 | Principal: 280 | Service: [ecs-tasks.amazonaws.com] 281 | Action: ['sts:AssumeRole'] 282 | Path: / 283 | Policies: 284 | - PolicyName: AmazonECSTaskExecutionRolePolicy 285 | PolicyDocument: 286 | Statement: 287 | - Effect: Allow 288 | Action: 289 | # Allow the ECS Tasks to download images from ECR 290 | - 'ecr:GetAuthorizationToken' 291 | - 'ecr:BatchCheckLayerAvailability' 292 | - 'ecr:GetDownloadUrlForLayer' 293 | - 'ecr:BatchGetImage' 294 | 295 | # Allow the ECS tasks to upload logs to CloudWatch 296 | - 'logs:CreateLogStream' 297 | - 'logs:PutLogEvents' 298 | Resource: '*' 299 | 300 | # These are the values output by the CloudFormation template. Be careful 301 | # about changing any of them, because of them are exported with specific 302 | # names so that the other task related CF templates can use them. 303 | Outputs: 304 | ClusterName: 305 | Description: The name of the ECS cluster 306 | Value: !Ref 'ECSCluster' 307 | Export: 308 | Name: !Sub ${EnvironmentName}:ClusterName 309 | AutoscalingRole: 310 | Description: The ARN of the role used for autoscaling 311 | Value: !GetAtt 'AutoscalingRole.Arn' 312 | Export: 313 | Name: !Sub ${EnvironmentName}:AutoscalingRole 314 | ECSRole: 315 | Description: The ARN of the ECS role 316 | Value: !GetAtt 'ECSRole.Arn' 317 | Export: 318 | Name: !Sub ${EnvironmentName}:ECSRole 319 | ECSTaskExecutionRole: 320 | Description: The ARN of the ECS role 321 | Value: !GetAtt 'ECSTaskExecutionRole.Arn' 322 | Export: 323 | Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole 324 | VpcId: 325 | Description: The ID of the VPC that this stack is deployed in 326 | Value: !Ref 'VPC' 327 | Export: 328 | Name: !Sub ${EnvironmentName}:VpcId 329 | PublicSubnetOne: 330 | Description: Public subnet one 331 | Value: !Ref 'PublicSubnetOne' 332 | Export: 333 | Name: !Sub ${EnvironmentName}:PublicSubnetOne 334 | PublicSubnetTwo: 335 | Description: Public subnet two 336 | Value: !Ref 'PublicSubnetTwo' 337 | Export: 338 | Name: !Sub ${EnvironmentName}:PublicSubnetTwo 339 | PrivateSubnetOne: 340 | Description: Private subnet one 341 | Value: !Ref 'PrivateSubnetOne' 342 | Export: 343 | Name: !Sub ${EnvironmentName}:PrivateSubnetOne 344 | PrivateSubnetTwo: 345 | Description: Private subnet two 346 | Value: !Ref 'PrivateSubnetTwo' 347 | Export: 348 | Name: !Sub ${EnvironmentName}:PrivateSubnetTwo 349 | ContainerSecurityGroup: 350 | Description: A security group used to allow Fargate containers to receive traffic 351 | Value: !Ref 'ContainerSecurityGroup' 352 | Export: 353 | Name: !Sub ${EnvironmentName}:ContainerSecurityGroup 354 | -------------------------------------------------------------------------------- /cluster/cluster-fargate-public-vpc.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: AWS Fargate cluster running containers in a public subnet. Only supports 3 | public facing load balancer, and public service discovery namespaces. 4 | Parameters: 5 | EnvironmentName: 6 | Type: String 7 | Default: production 8 | Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" 9 | Mappings: 10 | # Hard values for the subnet masks. These masks define 11 | # the range of internal IP addresses that can be assigned. 12 | # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 13 | # There are two subnets which cover the ranges: 14 | # 15 | # 10.0.0.0 - 10.0.0.255 16 | # 10.0.1.0 - 10.0.1.255 17 | # 18 | # If you need more IP addresses (perhaps you have so many 19 | # instances that you run out) then you can customize these 20 | # ranges to add more 21 | SubnetConfig: 22 | VPC: 23 | CIDR: '10.0.0.0/16' 24 | PublicOne: 25 | CIDR: '10.0.0.0/24' 26 | PublicTwo: 27 | CIDR: '10.0.1.0/24' 28 | Resources: 29 | # VPC in which containers will be networked. 30 | # It has two public subnets 31 | # We distribute the subnets across the first two available subnets 32 | # for the region, for high availability. 33 | VPC: 34 | Type: AWS::EC2::VPC 35 | Properties: 36 | EnableDnsSupport: true 37 | EnableDnsHostnames: true 38 | CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 39 | 40 | # Two public subnets, where containers can have public IP addresses 41 | PublicSubnetOne: 42 | Type: AWS::EC2::Subnet 43 | Properties: 44 | AvailabilityZone: 45 | Fn::Select: 46 | - 0 47 | - Fn::GetAZs: {Ref: 'AWS::Region'} 48 | VpcId: !Ref 'VPC' 49 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 50 | MapPublicIpOnLaunch: true 51 | PublicSubnetTwo: 52 | Type: AWS::EC2::Subnet 53 | Properties: 54 | AvailabilityZone: 55 | Fn::Select: 56 | - 1 57 | - Fn::GetAZs: {Ref: 'AWS::Region'} 58 | VpcId: !Ref 'VPC' 59 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 60 | MapPublicIpOnLaunch: true 61 | 62 | # Setup networking resources for the public subnets. Containers 63 | # in the public subnets have public IP addresses and the routing table 64 | # sends network traffic via the internet gateway. 65 | InternetGateway: 66 | Type: AWS::EC2::InternetGateway 67 | GatewayAttachement: 68 | Type: AWS::EC2::VPCGatewayAttachment 69 | Properties: 70 | VpcId: !Ref 'VPC' 71 | InternetGatewayId: !Ref 'InternetGateway' 72 | PublicRouteTable: 73 | Type: AWS::EC2::RouteTable 74 | Properties: 75 | VpcId: !Ref 'VPC' 76 | PublicRoute: 77 | Type: AWS::EC2::Route 78 | DependsOn: GatewayAttachement 79 | Properties: 80 | RouteTableId: !Ref 'PublicRouteTable' 81 | DestinationCidrBlock: '0.0.0.0/0' 82 | GatewayId: !Ref 'InternetGateway' 83 | PublicSubnetOneRouteTableAssociation: 84 | Type: AWS::EC2::SubnetRouteTableAssociation 85 | Properties: 86 | SubnetId: !Ref PublicSubnetOne 87 | RouteTableId: !Ref PublicRouteTable 88 | PublicSubnetTwoRouteTableAssociation: 89 | Type: AWS::EC2::SubnetRouteTableAssociation 90 | Properties: 91 | SubnetId: !Ref PublicSubnetTwo 92 | RouteTableId: !Ref PublicRouteTable 93 | 94 | # ECS Resources 95 | ECSCluster: 96 | Type: AWS::ECS::Cluster 97 | 98 | # A security group for the containers we will run in Fargate. 99 | # Rules are added to this security group based on what ingress you 100 | # add for the cluster. 101 | ContainerSecurityGroup: 102 | Type: AWS::EC2::SecurityGroup 103 | Properties: 104 | GroupDescription: Access to the Fargate containers 105 | VpcId: !Ref 'VPC' 106 | 107 | # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets 108 | # on your AWS account 109 | AutoscalingRole: 110 | Type: AWS::IAM::Role 111 | Properties: 112 | AssumeRolePolicyDocument: 113 | Statement: 114 | - Effect: Allow 115 | Principal: 116 | Service: [application-autoscaling.amazonaws.com] 117 | Action: ['sts:AssumeRole'] 118 | Path: / 119 | Policies: 120 | - PolicyName: service-autoscaling 121 | PolicyDocument: 122 | Statement: 123 | - Effect: Allow 124 | Action: 125 | - 'application-autoscaling:*' 126 | - 'cloudwatch:DescribeAlarms' 127 | - 'cloudwatch:PutMetricAlarm' 128 | - 'ecs:DescribeServices' 129 | - 'ecs:UpdateService' 130 | Resource: '*' 131 | 132 | # This is an IAM role which authorizes ECS to manage resources on your 133 | # account on your behalf, such as updating your load balancer with the 134 | # details of where your containers are, so that traffic can reach your 135 | # containers. 136 | ECSRole: 137 | Type: AWS::IAM::Role 138 | Properties: 139 | AssumeRolePolicyDocument: 140 | Statement: 141 | - Effect: Allow 142 | Principal: 143 | Service: [ecs.amazonaws.com] 144 | Action: ['sts:AssumeRole'] 145 | Path: / 146 | Policies: 147 | - PolicyName: ecs-service 148 | PolicyDocument: 149 | Statement: 150 | - Effect: Allow 151 | Action: 152 | # Rules which allow ECS to attach network interfaces to instances 153 | # on your behalf in order for awsvpc networking mode to work right 154 | - 'ec2:AttachNetworkInterface' 155 | - 'ec2:CreateNetworkInterface' 156 | - 'ec2:CreateNetworkInterfacePermission' 157 | - 'ec2:DeleteNetworkInterface' 158 | - 'ec2:DeleteNetworkInterfacePermission' 159 | - 'ec2:Describe*' 160 | - 'ec2:DetachNetworkInterface' 161 | 162 | # Rules which allow ECS to update load balancers on your behalf 163 | # with the information sabout how to send traffic to your containers 164 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 165 | - 'elasticloadbalancing:DeregisterTargets' 166 | - 'elasticloadbalancing:Describe*' 167 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 168 | - 'elasticloadbalancing:RegisterTargets' 169 | Resource: '*' 170 | 171 | # This is a role which is used by the ECS tasks themselves. 172 | ECSTaskExecutionRole: 173 | Type: AWS::IAM::Role 174 | Properties: 175 | AssumeRolePolicyDocument: 176 | Statement: 177 | - Effect: Allow 178 | Principal: 179 | Service: [ecs-tasks.amazonaws.com] 180 | Action: ['sts:AssumeRole'] 181 | Path: / 182 | Policies: 183 | - PolicyName: AmazonECSTaskExecutionRolePolicy 184 | PolicyDocument: 185 | Statement: 186 | - Effect: Allow 187 | Action: 188 | # Allow the ECS Tasks to download images from ECR 189 | - 'ecr:GetAuthorizationToken' 190 | - 'ecr:BatchCheckLayerAvailability' 191 | - 'ecr:GetDownloadUrlForLayer' 192 | - 'ecr:BatchGetImage' 193 | 194 | # Allow the ECS tasks to upload logs to CloudWatch 195 | - 'logs:CreateLogStream' 196 | - 'logs:PutLogEvents' 197 | Resource: '*' 198 | 199 | # These are the values output by the CloudFormation template. Be careful 200 | # about changing any of them, because of them are exported with specific 201 | # names so that the other task related CF templates can use them. 202 | Outputs: 203 | ClusterName: 204 | Description: The name of the ECS cluster 205 | Value: !Ref 'ECSCluster' 206 | Export: 207 | Name: !Sub ${EnvironmentName}:ClusterName 208 | AutoscalingRole: 209 | Description: The ARN of the role used for autoscaling 210 | Value: !GetAtt 'AutoscalingRole.Arn' 211 | Export: 212 | Name: !Sub ${EnvironmentName}:AutoscalingRole 213 | ECSRole: 214 | Description: The ARN of the ECS role 215 | Value: !GetAtt 'ECSRole.Arn' 216 | Export: 217 | Name: !Sub ${EnvironmentName}:ECSRole 218 | ECSTaskExecutionRole: 219 | Description: The ARN of the ECS role 220 | Value: !GetAtt 'ECSTaskExecutionRole.Arn' 221 | Export: 222 | Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole 223 | VpcId: 224 | Description: The ID of the VPC that this stack is deployed in 225 | Value: !Ref 'VPC' 226 | Export: 227 | Name: !Sub ${EnvironmentName}:VpcId 228 | PublicSubnetOne: 229 | Description: Public subnet one 230 | Value: !Ref 'PublicSubnetOne' 231 | Export: 232 | Name: !Sub ${EnvironmentName}:PublicSubnetOne 233 | PublicSubnetTwo: 234 | Description: Public subnet two 235 | Value: !Ref 'PublicSubnetTwo' 236 | Export: 237 | Name: !Sub ${EnvironmentName}:PublicSubnetTwo 238 | ContainerSecurityGroup: 239 | Description: A security group used to allow Fargate containers to receive traffic 240 | Value: !Ref 'ContainerSecurityGroup' 241 | Export: 242 | Name: !Sub ${EnvironmentName}:ContainerSecurityGroup 243 | -------------------------------------------------------------------------------- /images/private-subnet-private-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanpeck/ecs-cloudformation/6d274b43bbff12ba6960f0557270d2de85a0c8ce/images/private-subnet-private-lb.png -------------------------------------------------------------------------------- /images/private-subnet-private-service-discovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanpeck/ecs-cloudformation/6d274b43bbff12ba6960f0557270d2de85a0c8ce/images/private-subnet-private-service-discovery.png -------------------------------------------------------------------------------- /images/private-subnet-public-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanpeck/ecs-cloudformation/6d274b43bbff12ba6960f0557270d2de85a0c8ce/images/private-subnet-public-lb.png -------------------------------------------------------------------------------- /images/public-subnet-public-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanpeck/ecs-cloudformation/6d274b43bbff12ba6960f0557270d2de85a0c8ce/images/public-subnet-public-lb.png -------------------------------------------------------------------------------- /ingress/alb-external.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: External, public facing load balancer, for forwarding public traffic to containers 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this load balancer to 8 | Resources: 9 | EcsSecurityGroupIngressFromPublicALB: 10 | Type: AWS::EC2::SecurityGroupIngress 11 | Properties: 12 | Description: Ingress from the public ALB 13 | GroupId: 14 | Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 15 | IpProtocol: -1 16 | SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG' 17 | 18 | # Public load balancer, hosted in public subnets that is accessible 19 | # to the public, and is intended to route traffic to one or more public 20 | # facing services. This is used for accepting traffic from the public 21 | # internet and directing it to public facing microservices 22 | PublicLoadBalancerSG: 23 | Type: AWS::EC2::SecurityGroup 24 | Properties: 25 | GroupDescription: Access to the public facing load balancer 26 | VpcId: 27 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 28 | SecurityGroupIngress: 29 | # Allow access to ALB from anywhere on the internet 30 | - CidrIp: 0.0.0.0/0 31 | IpProtocol: -1 32 | PublicLoadBalancer: 33 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 34 | Properties: 35 | Scheme: internet-facing 36 | LoadBalancerAttributes: 37 | - Key: idle_timeout.timeout_seconds 38 | Value: '30' 39 | Subnets: 40 | # The load balancer is placed into the public subnets, so that traffic 41 | # from the internet can reach the load balancer directly via the internet gateway 42 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne 43 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo 44 | SecurityGroups: [!Ref 'PublicLoadBalancerSG'] 45 | # A dummy target group is used to setup the ALB to just drop traffic 46 | # initially, before any real service target groups have been added. 47 | DummyTargetGroupPublic: 48 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 49 | Properties: 50 | HealthCheckIntervalSeconds: 6 51 | HealthCheckPath: / 52 | HealthCheckProtocol: HTTP 53 | HealthCheckTimeoutSeconds: 5 54 | HealthyThresholdCount: 2 55 | Port: 80 56 | Protocol: HTTP 57 | UnhealthyThresholdCount: 2 58 | VpcId: 59 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 60 | PublicLoadBalancerListener: 61 | Type: AWS::ElasticLoadBalancingV2::Listener 62 | DependsOn: 63 | - PublicLoadBalancer 64 | Properties: 65 | DefaultActions: 66 | - TargetGroupArn: !Ref 'DummyTargetGroupPublic' 67 | Type: 'forward' 68 | LoadBalancerArn: !Ref 'PublicLoadBalancer' 69 | Port: 80 70 | Protocol: HTTP 71 | 72 | Outputs: 73 | PublicListener: 74 | Description: The ARN of the public load balancer's Listener 75 | Value: !Ref PublicLoadBalancerListener 76 | Export: 77 | Name: !Sub ${EnvironmentName}:PublicListener 78 | ExternalUrl: 79 | Description: The url of the external load balancer 80 | Value: !Sub http://${PublicLoadBalancer.DNSName} 81 | Export: 82 | Name: !Sub ${EnvironmentName}:ExternalUrl 83 | -------------------------------------------------------------------------------- /ingress/alb-internal.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Internal, private load balancer that only accepts traffic from 3 | containers in the cluster. 4 | Parameters: 5 | EnvironmentName: 6 | Type: String 7 | Default: production 8 | Description: The name of the environment to add this load balancer to 9 | Resources: 10 | EcsSecurityGroupIngressFromPrivateALB: 11 | Type: AWS::EC2::SecurityGroupIngress 12 | Properties: 13 | Description: Ingress from the private ALB 14 | GroupId: 15 | Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 16 | IpProtocol: -1 17 | SourceSecurityGroupId: !Ref 'PrivateLoadBalancerSG' 18 | 19 | # Private load balancer, hosted in private subnets, that only 20 | # accepts traffic from other containers in the Fargate cluster, and is 21 | # intended for private services that should not be accessed directly 22 | # by the public. 23 | PrivateLoadBalancerSG: 24 | Type: AWS::EC2::SecurityGroup 25 | Properties: 26 | GroupDescription: Access to the internal load balancer 27 | VpcId: 28 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 29 | PrivateLoadBalancerIngressFromECS: 30 | Type: AWS::EC2::SecurityGroupIngress 31 | Properties: 32 | Description: Only accept traffic from a container in the fargate container security group 33 | GroupId: !Ref 'PrivateLoadBalancerSG' 34 | IpProtocol: -1 35 | SourceSecurityGroupId: 36 | Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 37 | PrivateLoadBalancer: 38 | Type: AWS::ElasticLoadBalancingV2::LoadBalancer 39 | Properties: 40 | Scheme: internal 41 | LoadBalancerAttributes: 42 | - Key: idle_timeout.timeout_seconds 43 | Value: '30' 44 | Subnets: 45 | # This load balancer is put into the private subnet, so that there is no 46 | # route for the public to even be able to access the private load balancer. 47 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne 48 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo 49 | SecurityGroups: [!Ref 'PrivateLoadBalancerSG'] 50 | # This dummy target group is used to setup the ALB to just drop traffic 51 | # initially, before any real service target groups have been added. 52 | DummyTargetGroupPrivate: 53 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 54 | Properties: 55 | HealthCheckIntervalSeconds: 6 56 | HealthCheckPath: / 57 | HealthCheckProtocol: HTTP 58 | HealthCheckTimeoutSeconds: 5 59 | HealthyThresholdCount: 2 60 | Port: 80 61 | Protocol: HTTP 62 | UnhealthyThresholdCount: 2 63 | VpcId: 64 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 65 | PrivateLoadBalancerListener: 66 | Type: AWS::ElasticLoadBalancingV2::Listener 67 | DependsOn: 68 | - PrivateLoadBalancer 69 | Properties: 70 | DefaultActions: 71 | - TargetGroupArn: !Ref 'DummyTargetGroupPrivate' 72 | Type: 'forward' 73 | LoadBalancerArn: !Ref 'PrivateLoadBalancer' 74 | Port: 80 75 | Protocol: HTTP 76 | 77 | Outputs: 78 | PrivateListener: 79 | Description: The ARN of the public load balancer's Listener 80 | Value: !Ref PrivateLoadBalancerListener 81 | Export: 82 | Name: !Sub ${EnvironmentName}:PrivateListener 83 | InternalUrl: 84 | Description: The url of the internal load balancer 85 | Value: !Sub http://${PrivateLoadBalancer.DNSName} 86 | Export: 87 | Name: !Sub ${EnvironmentName}:InternalUrl 88 | -------------------------------------------------------------------------------- /ingress/service-discovery-internal.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: An internal service discovery namespace 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service discovery namespace to 8 | Domain: 9 | Type: String 10 | Default: service.production 11 | Description: The name of the namespace. Services are prepended, for example user.service.production 12 | Resources: 13 | 14 | # Rule which allows the containers to talk to other containers in the same group. 15 | # This is what allows a container to use service discovery to get the IP and talk 16 | # to another container in the same group. 17 | EcsSecurityGroupIngressFromSelf: 18 | Type: AWS::EC2::SecurityGroupIngress 19 | Properties: 20 | Description: Ingress from other containers in the cluster 21 | GroupId: 22 | Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 23 | IpProtocol: -1 24 | SourceSecurityGroupId: 25 | Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 26 | 27 | ServiceDiscoveryNamespace: 28 | Type: AWS::ServiceDiscovery::PrivateDnsNamespace 29 | Properties: 30 | Name: !Ref Domain 31 | Vpc: 32 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 33 | 34 | Outputs: 35 | PrivateServiceDiscoveryNamespace: 36 | Description: The ID of the private service discovery namespace 37 | Value: !Ref ServiceDiscoveryNamespace 38 | Export: 39 | Name: !Sub ${EnvironmentName}:PrivateServiceDiscoveryNamespace 40 | 41 | -------------------------------------------------------------------------------- /service/service-ec2-private-discovery.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service into an ECS cluster with service discovery endpoint 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | DesiredCount: 30 | Type: Number 31 | Default: 2 32 | Description: How many copies of the service task to run 33 | Role: 34 | Type: String 35 | Default: "" 36 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 37 | access other AWS resources like S3 buckets, DynamoDB tables, etc 38 | 39 | Conditions: 40 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 41 | 42 | Resources: 43 | # A log group for storing the stdout logs from this service's containers 44 | LogGroup: 45 | Type: AWS::Logs::LogGroup 46 | Properties: 47 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 48 | 49 | # The task definition. This is a simple metadata description of what 50 | # container to run, and what resource requirements it has. 51 | TaskDefinition: 52 | Type: AWS::ECS::TaskDefinition 53 | Properties: 54 | Family: !Ref 'ServiceName' 55 | Cpu: !Ref 'ContainerCpu' 56 | Memory: !Ref 'ContainerMemory' 57 | TaskRoleArn: 58 | Fn::If: 59 | - 'HasCustomRole' 60 | - !Ref 'Role' 61 | - !Ref "AWS::NoValue" 62 | ContainerDefinitions: 63 | - Name: !Ref 'ServiceName' 64 | Cpu: !Ref 'ContainerCpu' 65 | Memory: !Ref 'ContainerMemory' 66 | Image: !Ref 'ImageUrl' 67 | PortMappings: 68 | - ContainerPort: !Ref 'ContainerPort' 69 | LogConfiguration: 70 | LogDriver: 'awslogs' 71 | Options: 72 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 73 | awslogs-region: !Ref 'AWS::Region' 74 | awslogs-stream-prefix: !Ref 'ServiceName' 75 | 76 | # The service. The service is a resource which allows you to run multiple 77 | # copies of a type of task, and gather up their logs and metrics, as well 78 | # as monitor the number of running tasks and replace any that have crashed 79 | Service: 80 | Type: AWS::ECS::Service 81 | DependsOn: ServiceDiscoveryService 82 | Properties: 83 | ServiceName: !Ref 'ServiceName' 84 | Cluster: 85 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 86 | DeploymentConfiguration: 87 | MaximumPercent: 200 88 | MinimumHealthyPercent: 75 89 | DesiredCount: !Ref 'DesiredCount' 90 | TaskDefinition: !Ref 'TaskDefinition' 91 | ServiceRegistries: 92 | - RegistryArn: !GetAtt ServiceDiscoveryService.Arn 93 | ContainerPort: !Ref 'ContainerPort' 94 | ContainerName: !Ref 'ServiceName' 95 | 96 | # Create a service discovery service in the private service namespace. 97 | ServiceDiscoveryService: 98 | Type: AWS::ServiceDiscovery::Service 99 | Properties: 100 | Name: !Ref ServiceName 101 | DnsConfig: 102 | DnsRecords: [{Type: SRV, TTL: "10"}] 103 | NamespaceId: 104 | Fn::ImportValue: !Sub ${EnvironmentName}:PrivateServiceDiscoveryNamespace 105 | HealthCheckCustomConfig: 106 | FailureThreshold: 1 107 | 108 | # Enable autoscaling for this service 109 | ScalableTarget: 110 | Type: AWS::ApplicationAutoScaling::ScalableTarget 111 | DependsOn: Service 112 | Properties: 113 | ServiceNamespace: 'ecs' 114 | ScalableDimension: 'ecs:service:DesiredCount' 115 | ResourceId: 116 | Fn::Join: 117 | - '/' 118 | - - service 119 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 120 | - !Ref 'ServiceName' 121 | MinCapacity: 2 122 | MaxCapacity: 10 123 | RoleARN: 124 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 125 | 126 | # Create scaling policies for the service 127 | ScaleDownPolicy: 128 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 129 | DependsOn: ScalableTarget 130 | Properties: 131 | PolicyName: 132 | Fn::Join: 133 | - '/' 134 | - - scale 135 | - !Ref 'EnvironmentName' 136 | - !Ref 'ServiceName' 137 | - down 138 | PolicyType: StepScaling 139 | ResourceId: 140 | Fn::Join: 141 | - '/' 142 | - - service 143 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 144 | - !Ref 'ServiceName' 145 | ScalableDimension: 'ecs:service:DesiredCount' 146 | ServiceNamespace: 'ecs' 147 | StepScalingPolicyConfiguration: 148 | AdjustmentType: 'ChangeInCapacity' 149 | StepAdjustments: 150 | - MetricIntervalUpperBound: 0 151 | ScalingAdjustment: -1 152 | MetricAggregationType: 'Average' 153 | Cooldown: 60 154 | 155 | ScaleUpPolicy: 156 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 157 | DependsOn: ScalableTarget 158 | Properties: 159 | PolicyName: 160 | Fn::Join: 161 | - '/' 162 | - - scale 163 | - !Ref 'EnvironmentName' 164 | - !Ref 'ServiceName' 165 | - up 166 | PolicyType: StepScaling 167 | ResourceId: 168 | Fn::Join: 169 | - '/' 170 | - - service 171 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 172 | - !Ref 'ServiceName' 173 | ScalableDimension: 'ecs:service:DesiredCount' 174 | ServiceNamespace: 'ecs' 175 | StepScalingPolicyConfiguration: 176 | AdjustmentType: 'ChangeInCapacity' 177 | StepAdjustments: 178 | - MetricIntervalLowerBound: 0 179 | MetricIntervalUpperBound: 15 180 | ScalingAdjustment: 1 181 | - MetricIntervalLowerBound: 15 182 | MetricIntervalUpperBound: 25 183 | ScalingAdjustment: 2 184 | - MetricIntervalLowerBound: 25 185 | ScalingAdjustment: 3 186 | MetricAggregationType: 'Average' 187 | Cooldown: 60 188 | 189 | # Create alarms to trigger these policies 190 | LowCpuUsageAlarm: 191 | Type: AWS::CloudWatch::Alarm 192 | Properties: 193 | AlarmName: 194 | Fn::Join: 195 | - '-' 196 | - - low-cpu 197 | - !Ref 'EnvironmentName' 198 | - !Ref 'ServiceName' 199 | AlarmDescription: 200 | Fn::Join: 201 | - ' ' 202 | - - "Low CPU utilization for service" 203 | - !Ref 'ServiceName' 204 | - "in environment" 205 | - !Ref 'EnvironmentName' 206 | MetricName: CPUUtilization 207 | Namespace: AWS/ECS 208 | Dimensions: 209 | - Name: ServiceName 210 | Value: !Ref 'ServiceName' 211 | - Name: ClusterName 212 | Value: 213 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 214 | Statistic: Average 215 | Period: 60 216 | EvaluationPeriods: 1 217 | Threshold: 20 218 | ComparisonOperator: LessThanOrEqualToThreshold 219 | AlarmActions: 220 | - !Ref ScaleDownPolicy 221 | 222 | HighCpuUsageAlarm: 223 | Type: AWS::CloudWatch::Alarm 224 | Properties: 225 | AlarmName: 226 | Fn::Join: 227 | - '-' 228 | - - high-cpu 229 | - !Ref 'EnvironmentName' 230 | - !Ref 'ServiceName' 231 | AlarmDescription: 232 | Fn::Join: 233 | - ' ' 234 | - - "High CPU utilization for service" 235 | - !Ref 'ServiceName' 236 | - "in environment" 237 | - !Ref 'EnvironmentName' 238 | MetricName: CPUUtilization 239 | Namespace: AWS/ECS 240 | Dimensions: 241 | - Name: ServiceName 242 | Value: !Ref 'ServiceName' 243 | - Name: ClusterName 244 | Value: 245 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 246 | Statistic: Average 247 | Period: 60 248 | EvaluationPeriods: 1 249 | Threshold: 70 250 | ComparisonOperator: GreaterThanOrEqualToThreshold 251 | AlarmActions: 252 | - !Ref ScaleUpPolicy 253 | 254 | -------------------------------------------------------------------------------- /service/service-ec2-private-lb.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service into an ECS cluster behind a private load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | Path: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the public load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | Priority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if your have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Ref 'ServiceName' 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | TaskRoleArn: 70 | Fn::If: 71 | - 'HasCustomRole' 72 | - !Ref 'Role' 73 | - !Ref "AWS::NoValue" 74 | ContainerDefinitions: 75 | - Name: !Ref 'ServiceName' 76 | Cpu: !Ref 'ContainerCpu' 77 | Memory: !Ref 'ContainerMemory' 78 | Image: !Ref 'ImageUrl' 79 | PortMappings: 80 | - ContainerPort: !Ref 'ContainerPort' 81 | LogConfiguration: 82 | LogDriver: 'awslogs' 83 | Options: 84 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 85 | awslogs-region: !Ref 'AWS::Region' 86 | awslogs-stream-prefix: !Ref 'ServiceName' 87 | 88 | # The service. The service is a resource which allows you to run multiple 89 | # copies of a type of task, and gather up their logs and metrics, as well 90 | # as monitor the number of running tasks and replace any that have crashed 91 | Service: 92 | Type: AWS::ECS::Service 93 | DependsOn: LoadBalancerRule 94 | Properties: 95 | ServiceName: !Ref 'ServiceName' 96 | Cluster: 97 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 98 | DeploymentConfiguration: 99 | MaximumPercent: 200 100 | MinimumHealthyPercent: 75 101 | DesiredCount: !Ref 'DesiredCount' 102 | TaskDefinition: !Ref 'TaskDefinition' 103 | LoadBalancers: 104 | - ContainerName: !Ref 'ServiceName' 105 | ContainerPort: !Ref 'ContainerPort' 106 | TargetGroupArn: !Ref 'TargetGroup' 107 | 108 | # A target group. This is used for keeping track of all the tasks, and 109 | # what IP addresses / port numbers they have. You can query it yourself, 110 | # to use the addresses yourself, but most often this target group is just 111 | # connected to an application load balancer, or network load balancer, so 112 | # it can automatically distribute traffic across all the targets. 113 | TargetGroup: 114 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 115 | Properties: 116 | HealthCheckIntervalSeconds: 6 117 | HealthCheckPath: / 118 | HealthCheckProtocol: HTTP 119 | HealthCheckTimeoutSeconds: 5 120 | HealthyThresholdCount: 2 121 | Name: !Ref 'ServiceName' 122 | Port: 80 123 | Protocol: HTTP 124 | UnhealthyThresholdCount: 2 125 | VpcId: 126 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 127 | 128 | # Create a rule on the load balancer for routing traffic to the target group 129 | LoadBalancerRule: 130 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 131 | Properties: 132 | Actions: 133 | - TargetGroupArn: !Ref 'TargetGroup' 134 | Type: 'forward' 135 | Conditions: 136 | - Field: path-pattern 137 | Values: [!Ref 'Path'] 138 | ListenerArn: 139 | Fn::ImportValue: !Sub ${EnvironmentName}:PrivateListener 140 | Priority: !Ref 'Priority' 141 | 142 | # Enable autoscaling for this service 143 | ScalableTarget: 144 | Type: AWS::ApplicationAutoScaling::ScalableTarget 145 | DependsOn: Service 146 | Properties: 147 | ServiceNamespace: 'ecs' 148 | ScalableDimension: 'ecs:service:DesiredCount' 149 | ResourceId: 150 | Fn::Join: 151 | - '/' 152 | - - service 153 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 154 | - !Ref 'ServiceName' 155 | MinCapacity: 2 156 | MaxCapacity: 10 157 | RoleARN: 158 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 159 | 160 | # Create scaling policies for the service 161 | ScaleDownPolicy: 162 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 163 | DependsOn: ScalableTarget 164 | Properties: 165 | PolicyName: 166 | Fn::Join: 167 | - '/' 168 | - - scale 169 | - !Ref 'EnvironmentName' 170 | - !Ref 'ServiceName' 171 | - down 172 | PolicyType: StepScaling 173 | ResourceId: 174 | Fn::Join: 175 | - '/' 176 | - - service 177 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 178 | - !Ref 'ServiceName' 179 | ScalableDimension: 'ecs:service:DesiredCount' 180 | ServiceNamespace: 'ecs' 181 | StepScalingPolicyConfiguration: 182 | AdjustmentType: 'ChangeInCapacity' 183 | StepAdjustments: 184 | - MetricIntervalUpperBound: 0 185 | ScalingAdjustment: -1 186 | MetricAggregationType: 'Average' 187 | Cooldown: 60 188 | 189 | ScaleUpPolicy: 190 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 191 | DependsOn: ScalableTarget 192 | Properties: 193 | PolicyName: 194 | Fn::Join: 195 | - '/' 196 | - - scale 197 | - !Ref 'EnvironmentName' 198 | - !Ref 'ServiceName' 199 | - up 200 | PolicyType: StepScaling 201 | ResourceId: 202 | Fn::Join: 203 | - '/' 204 | - - service 205 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 206 | - !Ref 'ServiceName' 207 | ScalableDimension: 'ecs:service:DesiredCount' 208 | ServiceNamespace: 'ecs' 209 | StepScalingPolicyConfiguration: 210 | AdjustmentType: 'ChangeInCapacity' 211 | StepAdjustments: 212 | - MetricIntervalLowerBound: 0 213 | MetricIntervalUpperBound: 15 214 | ScalingAdjustment: 1 215 | - MetricIntervalLowerBound: 15 216 | MetricIntervalUpperBound: 25 217 | ScalingAdjustment: 2 218 | - MetricIntervalLowerBound: 25 219 | ScalingAdjustment: 3 220 | MetricAggregationType: 'Average' 221 | Cooldown: 60 222 | 223 | # Create alarms to trigger these policies 224 | LowCpuUsageAlarm: 225 | Type: AWS::CloudWatch::Alarm 226 | Properties: 227 | AlarmName: 228 | Fn::Join: 229 | - '-' 230 | - - low-cpu 231 | - !Ref 'EnvironmentName' 232 | - !Ref 'ServiceName' 233 | AlarmDescription: 234 | Fn::Join: 235 | - ' ' 236 | - - "Low CPU utilization for service" 237 | - !Ref 'ServiceName' 238 | - "in environment" 239 | - !Ref 'EnvironmentName' 240 | MetricName: CPUUtilization 241 | Namespace: AWS/ECS 242 | Dimensions: 243 | - Name: ServiceName 244 | Value: !Ref 'ServiceName' 245 | - Name: ClusterName 246 | Value: 247 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 248 | Statistic: Average 249 | Period: 60 250 | EvaluationPeriods: 1 251 | Threshold: 20 252 | ComparisonOperator: LessThanOrEqualToThreshold 253 | AlarmActions: 254 | - !Ref ScaleDownPolicy 255 | 256 | HighCpuUsageAlarm: 257 | Type: AWS::CloudWatch::Alarm 258 | Properties: 259 | AlarmName: 260 | Fn::Join: 261 | - '-' 262 | - - high-cpu 263 | - !Ref 'EnvironmentName' 264 | - !Ref 'ServiceName' 265 | AlarmDescription: 266 | Fn::Join: 267 | - ' ' 268 | - - "High CPU utilization for service" 269 | - !Ref 'ServiceName' 270 | - "in environment" 271 | - !Ref 'EnvironmentName' 272 | MetricName: CPUUtilization 273 | Namespace: AWS/ECS 274 | Dimensions: 275 | - Name: ServiceName 276 | Value: !Ref 'ServiceName' 277 | - Name: ClusterName 278 | Value: 279 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 280 | Statistic: Average 281 | Period: 60 282 | EvaluationPeriods: 1 283 | Threshold: 70 284 | ComparisonOperator: GreaterThanOrEqualToThreshold 285 | AlarmActions: 286 | - !Ref ScaleUpPolicy 287 | 288 | -------------------------------------------------------------------------------- /service/service-ec2-public-lb.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service into an ECS cluster behind a public load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | Path: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the public load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | Priority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if your have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Ref 'ServiceName' 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | TaskRoleArn: 70 | Fn::If: 71 | - 'HasCustomRole' 72 | - !Ref 'Role' 73 | - !Ref "AWS::NoValue" 74 | ContainerDefinitions: 75 | - Name: !Ref 'ServiceName' 76 | Cpu: !Ref 'ContainerCpu' 77 | Memory: !Ref 'ContainerMemory' 78 | Image: !Ref 'ImageUrl' 79 | PortMappings: 80 | - ContainerPort: !Ref 'ContainerPort' 81 | LogConfiguration: 82 | LogDriver: 'awslogs' 83 | Options: 84 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 85 | awslogs-region: !Ref 'AWS::Region' 86 | awslogs-stream-prefix: !Ref 'ServiceName' 87 | 88 | # The service. The service is a resource which allows you to run multiple 89 | # copies of a type of task, and gather up their logs and metrics, as well 90 | # as monitor the number of running tasks and replace any that have crashed 91 | Service: 92 | Type: AWS::ECS::Service 93 | DependsOn: LoadBalancerRule 94 | Properties: 95 | ServiceName: !Ref 'ServiceName' 96 | Cluster: 97 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 98 | DeploymentConfiguration: 99 | MaximumPercent: 200 100 | MinimumHealthyPercent: 75 101 | DesiredCount: !Ref 'DesiredCount' 102 | TaskDefinition: !Ref 'TaskDefinition' 103 | LoadBalancers: 104 | - ContainerName: !Ref 'ServiceName' 105 | ContainerPort: !Ref 'ContainerPort' 106 | TargetGroupArn: !Ref 'TargetGroup' 107 | 108 | # A target group. This is used for keeping track of all the tasks, and 109 | # what IP addresses / port numbers they have. You can query it yourself, 110 | # to use the addresses yourself, but most often this target group is just 111 | # connected to an application load balancer, or network load balancer, so 112 | # it can automatically distribute traffic across all the targets. 113 | TargetGroup: 114 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 115 | Properties: 116 | HealthCheckIntervalSeconds: 6 117 | HealthCheckPath: / 118 | HealthCheckProtocol: HTTP 119 | HealthCheckTimeoutSeconds: 5 120 | HealthyThresholdCount: 2 121 | Name: !Ref 'ServiceName' 122 | Port: 80 123 | Protocol: HTTP 124 | UnhealthyThresholdCount: 2 125 | VpcId: 126 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 127 | 128 | # Create a rule on the load balancer for routing traffic to the target group 129 | LoadBalancerRule: 130 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 131 | Properties: 132 | Actions: 133 | - TargetGroupArn: !Ref 'TargetGroup' 134 | Type: 'forward' 135 | Conditions: 136 | - Field: path-pattern 137 | Values: [!Ref 'Path'] 138 | ListenerArn: 139 | Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener 140 | Priority: !Ref 'Priority' 141 | 142 | # Enable autoscaling for this service 143 | ScalableTarget: 144 | Type: AWS::ApplicationAutoScaling::ScalableTarget 145 | DependsOn: Service 146 | Properties: 147 | ServiceNamespace: 'ecs' 148 | ScalableDimension: 'ecs:service:DesiredCount' 149 | ResourceId: 150 | Fn::Join: 151 | - '/' 152 | - - service 153 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 154 | - !Ref 'ServiceName' 155 | MinCapacity: 2 156 | MaxCapacity: 10 157 | RoleARN: 158 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 159 | 160 | # Create scaling policies for the service 161 | ScaleDownPolicy: 162 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 163 | DependsOn: ScalableTarget 164 | Properties: 165 | PolicyName: 166 | Fn::Join: 167 | - '/' 168 | - - scale 169 | - !Ref 'EnvironmentName' 170 | - !Ref 'ServiceName' 171 | - down 172 | PolicyType: StepScaling 173 | ResourceId: 174 | Fn::Join: 175 | - '/' 176 | - - service 177 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 178 | - !Ref 'ServiceName' 179 | ScalableDimension: 'ecs:service:DesiredCount' 180 | ServiceNamespace: 'ecs' 181 | StepScalingPolicyConfiguration: 182 | AdjustmentType: 'ChangeInCapacity' 183 | StepAdjustments: 184 | - MetricIntervalUpperBound: 0 185 | ScalingAdjustment: -1 186 | MetricAggregationType: 'Average' 187 | Cooldown: 60 188 | 189 | ScaleUpPolicy: 190 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 191 | DependsOn: ScalableTarget 192 | Properties: 193 | PolicyName: 194 | Fn::Join: 195 | - '/' 196 | - - scale 197 | - !Ref 'EnvironmentName' 198 | - !Ref 'ServiceName' 199 | - up 200 | PolicyType: StepScaling 201 | ResourceId: 202 | Fn::Join: 203 | - '/' 204 | - - service 205 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 206 | - !Ref 'ServiceName' 207 | ScalableDimension: 'ecs:service:DesiredCount' 208 | ServiceNamespace: 'ecs' 209 | StepScalingPolicyConfiguration: 210 | AdjustmentType: 'ChangeInCapacity' 211 | StepAdjustments: 212 | - MetricIntervalLowerBound: 0 213 | MetricIntervalUpperBound: 15 214 | ScalingAdjustment: 1 215 | - MetricIntervalLowerBound: 15 216 | MetricIntervalUpperBound: 25 217 | ScalingAdjustment: 2 218 | - MetricIntervalLowerBound: 25 219 | ScalingAdjustment: 3 220 | MetricAggregationType: 'Average' 221 | Cooldown: 60 222 | 223 | # Create alarms to trigger these policies 224 | LowCpuUsageAlarm: 225 | Type: AWS::CloudWatch::Alarm 226 | Properties: 227 | AlarmName: 228 | Fn::Join: 229 | - '-' 230 | - - low-cpu 231 | - !Ref 'EnvironmentName' 232 | - !Ref 'ServiceName' 233 | AlarmDescription: 234 | Fn::Join: 235 | - ' ' 236 | - - "Low CPU utilization for service" 237 | - !Ref 'ServiceName' 238 | - "in environment" 239 | - !Ref 'EnvironmentName' 240 | MetricName: CPUUtilization 241 | Namespace: AWS/ECS 242 | Dimensions: 243 | - Name: ServiceName 244 | Value: !Ref 'ServiceName' 245 | - Name: ClusterName 246 | Value: 247 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 248 | Statistic: Average 249 | Period: 60 250 | EvaluationPeriods: 1 251 | Threshold: 20 252 | ComparisonOperator: LessThanOrEqualToThreshold 253 | AlarmActions: 254 | - !Ref ScaleDownPolicy 255 | 256 | HighCpuUsageAlarm: 257 | Type: AWS::CloudWatch::Alarm 258 | Properties: 259 | AlarmName: 260 | Fn::Join: 261 | - '-' 262 | - - high-cpu 263 | - !Ref 'EnvironmentName' 264 | - !Ref 'ServiceName' 265 | AlarmDescription: 266 | Fn::Join: 267 | - ' ' 268 | - - "High CPU utilization for service" 269 | - !Ref 'ServiceName' 270 | - "in environment" 271 | - !Ref 'EnvironmentName' 272 | MetricName: CPUUtilization 273 | Namespace: AWS/ECS 274 | Dimensions: 275 | - Name: ServiceName 276 | Value: !Ref 'ServiceName' 277 | - Name: ClusterName 278 | Value: 279 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 280 | Statistic: Average 281 | Period: 60 282 | EvaluationPeriods: 1 283 | Threshold: 70 284 | ComparisonOperator: GreaterThanOrEqualToThreshold 285 | AlarmActions: 286 | - !Ref ScaleUpPolicy 287 | -------------------------------------------------------------------------------- /service/service-fargate-private-subnet-private-discovery.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service on AWS Fargate, hosted in a private subnet, behind a private load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | DesiredCount: 30 | Type: Number 31 | Default: 2 32 | Description: How many copies of the service task to run 33 | Role: 34 | Type: String 35 | Default: "" 36 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 37 | access other AWS resources like S3 buckets, DynamoDB tables, etc 38 | 39 | Conditions: 40 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 41 | 42 | Resources: 43 | # A log group for storing the stdout logs from this service's containers 44 | LogGroup: 45 | Type: AWS::Logs::LogGroup 46 | Properties: 47 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 48 | 49 | # The task definition. This is a simple metadata description of what 50 | # container to run, and what resource requirements it has. 51 | TaskDefinition: 52 | Type: AWS::ECS::TaskDefinition 53 | Properties: 54 | Family: !Ref 'ServiceName' 55 | Cpu: !Ref 'ContainerCpu' 56 | Memory: !Ref 'ContainerMemory' 57 | NetworkMode: awsvpc 58 | RequiresCompatibilities: 59 | - FARGATE 60 | ExecutionRoleArn: 61 | Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole 62 | TaskRoleArn: 63 | Fn::If: 64 | - 'HasCustomRole' 65 | - !Ref 'Role' 66 | - !Ref "AWS::NoValue" 67 | ContainerDefinitions: 68 | - Name: !Ref 'ServiceName' 69 | Cpu: !Ref 'ContainerCpu' 70 | Memory: !Ref 'ContainerMemory' 71 | Image: !Ref 'ImageUrl' 72 | PortMappings: 73 | - ContainerPort: !Ref 'ContainerPort' 74 | LogConfiguration: 75 | LogDriver: 'awslogs' 76 | Options: 77 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 78 | awslogs-region: !Ref 'AWS::Region' 79 | awslogs-stream-prefix: !Ref 'ServiceName' 80 | 81 | # The service. The service is a resource which allows you to run multiple 82 | # copies of a type of task, and gather up their logs and metrics, as well 83 | # as monitor the number of running tasks and replace any that have crashed 84 | Service: 85 | Type: AWS::ECS::Service 86 | Properties: 87 | ServiceName: !Ref 'ServiceName' 88 | Cluster: 89 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 90 | LaunchType: FARGATE 91 | DeploymentConfiguration: 92 | MaximumPercent: 200 93 | MinimumHealthyPercent: 75 94 | DesiredCount: !Ref 'DesiredCount' 95 | NetworkConfiguration: 96 | AwsvpcConfiguration: 97 | SecurityGroups: 98 | - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 99 | Subnets: 100 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne 101 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo 102 | TaskDefinition: !Ref 'TaskDefinition' 103 | ServiceRegistries: 104 | - RegistryArn: !GetAtt ServiceDiscoveryService.Arn 105 | 106 | # Create a service discovery service in the private service namespace. 107 | ServiceDiscoveryService: 108 | Type: AWS::ServiceDiscovery::Service 109 | Properties: 110 | Name: !Ref ServiceName 111 | DnsConfig: 112 | DnsRecords: [{Type: A, TTL: "10"}] 113 | NamespaceId: 114 | Fn::ImportValue: !Sub ${EnvironmentName}:PrivateServiceDiscoveryNamespace 115 | HealthCheckCustomConfig: 116 | FailureThreshold: 1 117 | 118 | # Enable autoscaling for this service 119 | ScalableTarget: 120 | Type: AWS::ApplicationAutoScaling::ScalableTarget 121 | DependsOn: Service 122 | Properties: 123 | ServiceNamespace: 'ecs' 124 | ScalableDimension: 'ecs:service:DesiredCount' 125 | ResourceId: 126 | Fn::Join: 127 | - '/' 128 | - - service 129 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 130 | - !Ref 'ServiceName' 131 | MinCapacity: 2 132 | MaxCapacity: 10 133 | RoleARN: 134 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 135 | 136 | # Create scaling policies for the service 137 | ScaleDownPolicy: 138 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 139 | DependsOn: ScalableTarget 140 | Properties: 141 | PolicyName: 142 | Fn::Join: 143 | - '/' 144 | - - scale 145 | - !Ref 'EnvironmentName' 146 | - !Ref 'ServiceName' 147 | - down 148 | PolicyType: StepScaling 149 | ResourceId: 150 | Fn::Join: 151 | - '/' 152 | - - service 153 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 154 | - !Ref 'ServiceName' 155 | ScalableDimension: 'ecs:service:DesiredCount' 156 | ServiceNamespace: 'ecs' 157 | StepScalingPolicyConfiguration: 158 | AdjustmentType: 'ChangeInCapacity' 159 | StepAdjustments: 160 | - MetricIntervalUpperBound: 0 161 | ScalingAdjustment: -1 162 | MetricAggregationType: 'Average' 163 | Cooldown: 60 164 | 165 | ScaleUpPolicy: 166 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 167 | DependsOn: ScalableTarget 168 | Properties: 169 | PolicyName: 170 | Fn::Join: 171 | - '/' 172 | - - scale 173 | - !Ref 'EnvironmentName' 174 | - !Ref 'ServiceName' 175 | - up 176 | PolicyType: StepScaling 177 | ResourceId: 178 | Fn::Join: 179 | - '/' 180 | - - service 181 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 182 | - !Ref 'ServiceName' 183 | ScalableDimension: 'ecs:service:DesiredCount' 184 | ServiceNamespace: 'ecs' 185 | StepScalingPolicyConfiguration: 186 | AdjustmentType: 'ChangeInCapacity' 187 | StepAdjustments: 188 | - MetricIntervalLowerBound: 0 189 | MetricIntervalUpperBound: 15 190 | ScalingAdjustment: 1 191 | - MetricIntervalLowerBound: 15 192 | MetricIntervalUpperBound: 25 193 | ScalingAdjustment: 2 194 | - MetricIntervalLowerBound: 25 195 | ScalingAdjustment: 3 196 | MetricAggregationType: 'Average' 197 | Cooldown: 60 198 | 199 | # Create alarms to trigger these policies 200 | LowCpuUsageAlarm: 201 | Type: AWS::CloudWatch::Alarm 202 | Properties: 203 | AlarmName: 204 | Fn::Join: 205 | - '-' 206 | - - low-cpu 207 | - !Ref 'EnvironmentName' 208 | - !Ref 'ServiceName' 209 | AlarmDescription: 210 | Fn::Join: 211 | - ' ' 212 | - - "Low CPU utilization for service" 213 | - !Ref 'ServiceName' 214 | - "in environment" 215 | - !Ref 'EnvironmentName' 216 | MetricName: CPUUtilization 217 | Namespace: AWS/ECS 218 | Dimensions: 219 | - Name: ServiceName 220 | Value: !Ref 'ServiceName' 221 | - Name: ClusterName 222 | Value: 223 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 224 | Statistic: Average 225 | Period: 60 226 | EvaluationPeriods: 1 227 | Threshold: 20 228 | ComparisonOperator: LessThanOrEqualToThreshold 229 | AlarmActions: 230 | - !Ref ScaleDownPolicy 231 | 232 | HighCpuUsageAlarm: 233 | Type: AWS::CloudWatch::Alarm 234 | Properties: 235 | AlarmName: 236 | Fn::Join: 237 | - '-' 238 | - - high-cpu 239 | - !Ref 'EnvironmentName' 240 | - !Ref 'ServiceName' 241 | AlarmDescription: 242 | Fn::Join: 243 | - ' ' 244 | - - "High CPU utilization for service" 245 | - !Ref 'ServiceName' 246 | - "in environment" 247 | - !Ref 'EnvironmentName' 248 | MetricName: CPUUtilization 249 | Namespace: AWS/ECS 250 | Dimensions: 251 | - Name: ServiceName 252 | Value: !Ref 'ServiceName' 253 | - Name: ClusterName 254 | Value: 255 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 256 | Statistic: Average 257 | Period: 60 258 | EvaluationPeriods: 1 259 | Threshold: 70 260 | ComparisonOperator: GreaterThanOrEqualToThreshold 261 | AlarmActions: 262 | - !Ref ScaleUpPolicy 263 | -------------------------------------------------------------------------------- /service/service-fargate-private-subnet-private-lb.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service on AWS Fargate, hosted in a private subnet, behind a private load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | Path: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | Priority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if your have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Ref 'ServiceName' 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | NetworkMode: awsvpc 70 | RequiresCompatibilities: 71 | - FARGATE 72 | ExecutionRoleArn: 73 | Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole 74 | TaskRoleArn: 75 | Fn::If: 76 | - 'HasCustomRole' 77 | - !Ref 'Role' 78 | - !Ref "AWS::NoValue" 79 | ContainerDefinitions: 80 | - Name: !Ref 'ServiceName' 81 | Cpu: !Ref 'ContainerCpu' 82 | Memory: !Ref 'ContainerMemory' 83 | Image: !Ref 'ImageUrl' 84 | PortMappings: 85 | - ContainerPort: !Ref 'ContainerPort' 86 | LogConfiguration: 87 | LogDriver: 'awslogs' 88 | Options: 89 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 90 | awslogs-region: !Ref 'AWS::Region' 91 | awslogs-stream-prefix: !Ref 'ServiceName' 92 | 93 | # The service. The service is a resource which allows you to run multiple 94 | # copies of a type of task, and gather up their logs and metrics, as well 95 | # as monitor the number of running tasks and replace any that have crashed 96 | Service: 97 | Type: AWS::ECS::Service 98 | DependsOn: LoadBalancerRule 99 | Properties: 100 | ServiceName: !Ref 'ServiceName' 101 | Cluster: 102 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 103 | LaunchType: FARGATE 104 | DeploymentConfiguration: 105 | MaximumPercent: 200 106 | MinimumHealthyPercent: 75 107 | DesiredCount: !Ref 'DesiredCount' 108 | NetworkConfiguration: 109 | AwsvpcConfiguration: 110 | SecurityGroups: 111 | - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 112 | Subnets: 113 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne 114 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo 115 | TaskDefinition: !Ref 'TaskDefinition' 116 | LoadBalancers: 117 | - ContainerName: !Ref 'ServiceName' 118 | ContainerPort: !Ref 'ContainerPort' 119 | TargetGroupArn: !Ref 'TargetGroup' 120 | 121 | # A target group. This is used for keeping track of all the tasks, and 122 | # what IP addresses / port numbers they have. You can query it yourself, 123 | # to use the addresses yourself, but most often this target group is just 124 | # connected to an application load balancer, or network load balancer, so 125 | # it can automatically distribute traffic across all the targets. 126 | TargetGroup: 127 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 128 | Properties: 129 | HealthCheckIntervalSeconds: 6 130 | HealthCheckPath: / 131 | HealthCheckProtocol: HTTP 132 | HealthCheckTimeoutSeconds: 5 133 | HealthyThresholdCount: 2 134 | TargetType: ip 135 | Name: !Ref 'ServiceName' 136 | Port: !Ref 'ContainerPort' 137 | Protocol: HTTP 138 | UnhealthyThresholdCount: 2 139 | VpcId: 140 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 141 | 142 | # Create a rule on the load balancer for routing traffic to the target group 143 | LoadBalancerRule: 144 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 145 | Properties: 146 | Actions: 147 | - TargetGroupArn: !Ref 'TargetGroup' 148 | Type: 'forward' 149 | Conditions: 150 | - Field: path-pattern 151 | Values: [!Ref 'Path'] 152 | ListenerArn: 153 | Fn::ImportValue: !Sub ${EnvironmentName}:PrivateListener 154 | Priority: !Ref 'Priority' 155 | 156 | # Enable autoscaling for this service 157 | ScalableTarget: 158 | Type: AWS::ApplicationAutoScaling::ScalableTarget 159 | DependsOn: Service 160 | Properties: 161 | ServiceNamespace: 'ecs' 162 | ScalableDimension: 'ecs:service:DesiredCount' 163 | ResourceId: 164 | Fn::Join: 165 | - '/' 166 | - - service 167 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 168 | - !Ref 'ServiceName' 169 | MinCapacity: 2 170 | MaxCapacity: 10 171 | RoleARN: 172 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 173 | 174 | # Create scaling policies for the service 175 | ScaleDownPolicy: 176 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 177 | DependsOn: ScalableTarget 178 | Properties: 179 | PolicyName: 180 | Fn::Join: 181 | - '/' 182 | - - scale 183 | - !Ref 'EnvironmentName' 184 | - !Ref 'ServiceName' 185 | - down 186 | PolicyType: StepScaling 187 | ResourceId: 188 | Fn::Join: 189 | - '/' 190 | - - service 191 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 192 | - !Ref 'ServiceName' 193 | ScalableDimension: 'ecs:service:DesiredCount' 194 | ServiceNamespace: 'ecs' 195 | StepScalingPolicyConfiguration: 196 | AdjustmentType: 'ChangeInCapacity' 197 | StepAdjustments: 198 | - MetricIntervalUpperBound: 0 199 | ScalingAdjustment: -1 200 | MetricAggregationType: 'Average' 201 | Cooldown: 60 202 | 203 | ScaleUpPolicy: 204 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 205 | DependsOn: ScalableTarget 206 | Properties: 207 | PolicyName: 208 | Fn::Join: 209 | - '/' 210 | - - scale 211 | - !Ref 'EnvironmentName' 212 | - !Ref 'ServiceName' 213 | - up 214 | PolicyType: StepScaling 215 | ResourceId: 216 | Fn::Join: 217 | - '/' 218 | - - service 219 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 220 | - !Ref 'ServiceName' 221 | ScalableDimension: 'ecs:service:DesiredCount' 222 | ServiceNamespace: 'ecs' 223 | StepScalingPolicyConfiguration: 224 | AdjustmentType: 'ChangeInCapacity' 225 | StepAdjustments: 226 | - MetricIntervalLowerBound: 0 227 | MetricIntervalUpperBound: 15 228 | ScalingAdjustment: 1 229 | - MetricIntervalLowerBound: 15 230 | MetricIntervalUpperBound: 25 231 | ScalingAdjustment: 2 232 | - MetricIntervalLowerBound: 25 233 | ScalingAdjustment: 3 234 | MetricAggregationType: 'Average' 235 | Cooldown: 60 236 | 237 | # Create alarms to trigger these policies 238 | LowCpuUsageAlarm: 239 | Type: AWS::CloudWatch::Alarm 240 | Properties: 241 | AlarmName: 242 | Fn::Join: 243 | - '-' 244 | - - low-cpu 245 | - !Ref 'EnvironmentName' 246 | - !Ref 'ServiceName' 247 | AlarmDescription: 248 | Fn::Join: 249 | - ' ' 250 | - - "Low CPU utilization for service" 251 | - !Ref 'ServiceName' 252 | - "in environment" 253 | - !Ref 'EnvironmentName' 254 | MetricName: CPUUtilization 255 | Namespace: AWS/ECS 256 | Dimensions: 257 | - Name: ServiceName 258 | Value: !Ref 'ServiceName' 259 | - Name: ClusterName 260 | Value: 261 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 262 | Statistic: Average 263 | Period: 60 264 | EvaluationPeriods: 1 265 | Threshold: 20 266 | ComparisonOperator: LessThanOrEqualToThreshold 267 | AlarmActions: 268 | - !Ref ScaleDownPolicy 269 | 270 | HighCpuUsageAlarm: 271 | Type: AWS::CloudWatch::Alarm 272 | Properties: 273 | AlarmName: 274 | Fn::Join: 275 | - '-' 276 | - - high-cpu 277 | - !Ref 'EnvironmentName' 278 | - !Ref 'ServiceName' 279 | AlarmDescription: 280 | Fn::Join: 281 | - ' ' 282 | - - "High CPU utilization for service" 283 | - !Ref 'ServiceName' 284 | - "in environment" 285 | - !Ref 'EnvironmentName' 286 | MetricName: CPUUtilization 287 | Namespace: AWS/ECS 288 | Dimensions: 289 | - Name: ServiceName 290 | Value: !Ref 'ServiceName' 291 | - Name: ClusterName 292 | Value: 293 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 294 | Statistic: Average 295 | Period: 60 296 | EvaluationPeriods: 1 297 | Threshold: 70 298 | ComparisonOperator: GreaterThanOrEqualToThreshold 299 | AlarmActions: 300 | - !Ref ScaleUpPolicy 301 | -------------------------------------------------------------------------------- /service/service-fargate-private-subnet-public-lb.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service on AWS Fargate, hosted in a private subnet, but accessible via a public load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | Path: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | Priority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if your have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Ref 'ServiceName' 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | NetworkMode: awsvpc 70 | RequiresCompatibilities: 71 | - FARGATE 72 | ExecutionRoleArn: 73 | Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole 74 | TaskRoleArn: 75 | Fn::If: 76 | - 'HasCustomRole' 77 | - !Ref 'Role' 78 | - !Ref "AWS::NoValue" 79 | ContainerDefinitions: 80 | - Name: !Ref 'ServiceName' 81 | Cpu: !Ref 'ContainerCpu' 82 | Memory: !Ref 'ContainerMemory' 83 | Image: !Ref 'ImageUrl' 84 | PortMappings: 85 | - ContainerPort: !Ref 'ContainerPort' 86 | LogConfiguration: 87 | LogDriver: 'awslogs' 88 | Options: 89 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 90 | awslogs-region: !Ref 'AWS::Region' 91 | awslogs-stream-prefix: !Ref 'ServiceName' 92 | 93 | # The service. The service is a resource which allows you to run multiple 94 | # copies of a type of task, and gather up their logs and metrics, as well 95 | # as monitor the number of running tasks and replace any that have crashed 96 | Service: 97 | Type: AWS::ECS::Service 98 | DependsOn: LoadBalancerRule 99 | Properties: 100 | ServiceName: !Ref 'ServiceName' 101 | Cluster: 102 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 103 | LaunchType: FARGATE 104 | DeploymentConfiguration: 105 | MaximumPercent: 200 106 | MinimumHealthyPercent: 75 107 | DesiredCount: !Ref 'DesiredCount' 108 | NetworkConfiguration: 109 | AwsvpcConfiguration: 110 | AssignPublicIp: ENABLED 111 | SecurityGroups: 112 | - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 113 | Subnets: 114 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne 115 | - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo 116 | TaskDefinition: !Ref 'TaskDefinition' 117 | LoadBalancers: 118 | - ContainerName: !Ref 'ServiceName' 119 | ContainerPort: !Ref 'ContainerPort' 120 | TargetGroupArn: !Ref 'TargetGroup' 121 | 122 | # A target group. This is used for keeping track of all the tasks, and 123 | # what IP addresses / port numbers they have. You can query it yourself, 124 | # to use the addresses yourself, but most often this target group is just 125 | # connected to an application load balancer, or network load balancer, so 126 | # it can automatically distribute traffic across all the targets. 127 | TargetGroup: 128 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 129 | Properties: 130 | HealthCheckIntervalSeconds: 6 131 | HealthCheckPath: / 132 | HealthCheckProtocol: HTTP 133 | HealthCheckTimeoutSeconds: 5 134 | HealthyThresholdCount: 2 135 | TargetType: ip 136 | Name: !Ref 'ServiceName' 137 | Port: !Ref 'ContainerPort' 138 | Protocol: HTTP 139 | UnhealthyThresholdCount: 2 140 | VpcId: 141 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 142 | 143 | # Create a rule on the load balancer for routing traffic to the target group 144 | LoadBalancerRule: 145 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 146 | Properties: 147 | Actions: 148 | - TargetGroupArn: !Ref 'TargetGroup' 149 | Type: 'forward' 150 | Conditions: 151 | - Field: path-pattern 152 | Values: [!Ref 'Path'] 153 | ListenerArn: 154 | Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener 155 | Priority: !Ref 'Priority' 156 | 157 | # Enable autoscaling for this service 158 | ScalableTarget: 159 | Type: AWS::ApplicationAutoScaling::ScalableTarget 160 | DependsOn: Service 161 | Properties: 162 | ServiceNamespace: 'ecs' 163 | ScalableDimension: 'ecs:service:DesiredCount' 164 | ResourceId: 165 | Fn::Join: 166 | - '/' 167 | - - service 168 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 169 | - !Ref 'ServiceName' 170 | MinCapacity: 2 171 | MaxCapacity: 10 172 | RoleARN: 173 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 174 | 175 | # Create scaling policies for the service 176 | ScaleDownPolicy: 177 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 178 | DependsOn: ScalableTarget 179 | Properties: 180 | PolicyName: 181 | Fn::Join: 182 | - '/' 183 | - - scale 184 | - !Ref 'EnvironmentName' 185 | - !Ref 'ServiceName' 186 | - down 187 | PolicyType: StepScaling 188 | ResourceId: 189 | Fn::Join: 190 | - '/' 191 | - - service 192 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 193 | - !Ref 'ServiceName' 194 | ScalableDimension: 'ecs:service:DesiredCount' 195 | ServiceNamespace: 'ecs' 196 | StepScalingPolicyConfiguration: 197 | AdjustmentType: 'ChangeInCapacity' 198 | StepAdjustments: 199 | - MetricIntervalUpperBound: 0 200 | ScalingAdjustment: -1 201 | MetricAggregationType: 'Average' 202 | Cooldown: 60 203 | 204 | ScaleUpPolicy: 205 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 206 | DependsOn: ScalableTarget 207 | Properties: 208 | PolicyName: 209 | Fn::Join: 210 | - '/' 211 | - - scale 212 | - !Ref 'EnvironmentName' 213 | - !Ref 'ServiceName' 214 | - up 215 | PolicyType: StepScaling 216 | ResourceId: 217 | Fn::Join: 218 | - '/' 219 | - - service 220 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 221 | - !Ref 'ServiceName' 222 | ScalableDimension: 'ecs:service:DesiredCount' 223 | ServiceNamespace: 'ecs' 224 | StepScalingPolicyConfiguration: 225 | AdjustmentType: 'ChangeInCapacity' 226 | StepAdjustments: 227 | - MetricIntervalLowerBound: 0 228 | MetricIntervalUpperBound: 15 229 | ScalingAdjustment: 1 230 | - MetricIntervalLowerBound: 15 231 | MetricIntervalUpperBound: 25 232 | ScalingAdjustment: 2 233 | - MetricIntervalLowerBound: 25 234 | ScalingAdjustment: 3 235 | MetricAggregationType: 'Average' 236 | Cooldown: 60 237 | 238 | # Create alarms to trigger these policies 239 | LowCpuUsageAlarm: 240 | Type: AWS::CloudWatch::Alarm 241 | Properties: 242 | AlarmName: 243 | Fn::Join: 244 | - '-' 245 | - - low-cpu 246 | - !Ref 'EnvironmentName' 247 | - !Ref 'ServiceName' 248 | AlarmDescription: 249 | Fn::Join: 250 | - ' ' 251 | - - "Low CPU utilization for service" 252 | - !Ref 'ServiceName' 253 | - "in environment" 254 | - !Ref 'EnvironmentName' 255 | MetricName: CPUUtilization 256 | Namespace: AWS/ECS 257 | Dimensions: 258 | - Name: ServiceName 259 | Value: !Ref 'ServiceName' 260 | - Name: ClusterName 261 | Value: 262 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 263 | Statistic: Average 264 | Period: 60 265 | EvaluationPeriods: 1 266 | Threshold: 20 267 | ComparisonOperator: LessThanOrEqualToThreshold 268 | AlarmActions: 269 | - !Ref ScaleDownPolicy 270 | 271 | HighCpuUsageAlarm: 272 | Type: AWS::CloudWatch::Alarm 273 | Properties: 274 | AlarmName: 275 | Fn::Join: 276 | - '-' 277 | - - high-cpu 278 | - !Ref 'EnvironmentName' 279 | - !Ref 'ServiceName' 280 | AlarmDescription: 281 | Fn::Join: 282 | - ' ' 283 | - - "High CPU utilization for service" 284 | - !Ref 'ServiceName' 285 | - "in environment" 286 | - !Ref 'EnvironmentName' 287 | MetricName: CPUUtilization 288 | Namespace: AWS/ECS 289 | Dimensions: 290 | - Name: ServiceName 291 | Value: !Ref 'ServiceName' 292 | - Name: ClusterName 293 | Value: 294 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 295 | Statistic: Average 296 | Period: 60 297 | EvaluationPeriods: 1 298 | Threshold: 70 299 | ComparisonOperator: GreaterThanOrEqualToThreshold 300 | AlarmActions: 301 | - !Ref ScaleUpPolicy 302 | -------------------------------------------------------------------------------- /service/service-fargate-public-subnet-public-lb.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: Deploy a service on AWS Fargate, hosted in a public subnet, and accessible via a public load balancer. 3 | Parameters: 4 | EnvironmentName: 5 | Type: String 6 | Default: production 7 | Description: The name of the environment to add this service to 8 | ServiceName: 9 | Type: String 10 | Default: nginx 11 | Description: A name for the service 12 | ImageUrl: 13 | Type: String 14 | Default: nginx 15 | Description: The url of a docker image that contains the application process that 16 | will handle the traffic for this service 17 | ContainerPort: 18 | Type: Number 19 | Default: 80 20 | Description: What port number the application inside the docker container is binding to 21 | ContainerCpu: 22 | Type: Number 23 | Default: 256 24 | Description: How much CPU to give the container. 1024 is 1 CPU 25 | ContainerMemory: 26 | Type: Number 27 | Default: 512 28 | Description: How much memory in megabytes to give the container 29 | Path: 30 | Type: String 31 | Default: "*" 32 | Description: A path on the load balancer that this service 33 | should be connected to. Use * to send all load balancer 34 | traffic to this service. 35 | Priority: 36 | Type: Number 37 | Default: 1 38 | Description: The priority for the routing rule added to the load balancer. 39 | This only applies if your have multiple services which have been 40 | assigned to different paths on the load balancer. 41 | DesiredCount: 42 | Type: Number 43 | Default: 2 44 | Description: How many copies of the service task to run 45 | Role: 46 | Type: String 47 | Default: "" 48 | Description: (Optional) An IAM role to give the service's containers if the code within needs to 49 | access other AWS resources like S3 buckets, DynamoDB tables, etc 50 | 51 | Conditions: 52 | HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] 53 | 54 | Resources: 55 | # A log group for storing the stdout logs from this service's containers 56 | LogGroup: 57 | Type: AWS::Logs::LogGroup 58 | Properties: 59 | LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} 60 | 61 | # The task definition. This is a simple metadata description of what 62 | # container to run, and what resource requirements it has. 63 | TaskDefinition: 64 | Type: AWS::ECS::TaskDefinition 65 | Properties: 66 | Family: !Ref 'ServiceName' 67 | Cpu: !Ref 'ContainerCpu' 68 | Memory: !Ref 'ContainerMemory' 69 | NetworkMode: awsvpc 70 | RequiresCompatibilities: 71 | - FARGATE 72 | ExecutionRoleArn: 73 | Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole 74 | TaskRoleArn: 75 | Fn::If: 76 | - 'HasCustomRole' 77 | - !Ref 'Role' 78 | - !Ref "AWS::NoValue" 79 | ContainerDefinitions: 80 | - Name: !Ref 'ServiceName' 81 | Cpu: !Ref 'ContainerCpu' 82 | Memory: !Ref 'ContainerMemory' 83 | Image: !Ref 'ImageUrl' 84 | PortMappings: 85 | - ContainerPort: !Ref 'ContainerPort' 86 | LogConfiguration: 87 | LogDriver: 'awslogs' 88 | Options: 89 | awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} 90 | awslogs-region: !Ref 'AWS::Region' 91 | awslogs-stream-prefix: !Ref 'ServiceName' 92 | 93 | # The service. The service is a resource which allows you to run multiple 94 | # copies of a type of task, and gather up their logs and metrics, as well 95 | # as monitor the number of running tasks and replace any that have crashed 96 | Service: 97 | Type: AWS::ECS::Service 98 | DependsOn: LoadBalancerRule 99 | Properties: 100 | ServiceName: !Ref 'ServiceName' 101 | Cluster: 102 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 103 | LaunchType: FARGATE 104 | DeploymentConfiguration: 105 | MaximumPercent: 200 106 | MinimumHealthyPercent: 75 107 | DesiredCount: !Ref 'DesiredCount' 108 | NetworkConfiguration: 109 | AwsvpcConfiguration: 110 | AssignPublicIp: ENABLED 111 | SecurityGroups: 112 | - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup 113 | Subnets: 114 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne 115 | - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo 116 | TaskDefinition: !Ref 'TaskDefinition' 117 | LoadBalancers: 118 | - ContainerName: !Ref 'ServiceName' 119 | ContainerPort: !Ref 'ContainerPort' 120 | TargetGroupArn: !Ref 'TargetGroup' 121 | 122 | # A target group. This is used for keeping track of all the tasks, and 123 | # what IP addresses / port numbers they have. You can query it yourself, 124 | # to use the addresses yourself, but most often this target group is just 125 | # connected to an application load balancer, or network load balancer, so 126 | # it can automatically distribute traffic across all the targets. 127 | TargetGroup: 128 | Type: AWS::ElasticLoadBalancingV2::TargetGroup 129 | Properties: 130 | HealthCheckIntervalSeconds: 6 131 | HealthCheckPath: / 132 | HealthCheckProtocol: HTTP 133 | HealthCheckTimeoutSeconds: 5 134 | HealthyThresholdCount: 2 135 | TargetType: ip 136 | Name: !Ref 'ServiceName' 137 | Port: !Ref 'ContainerPort' 138 | Protocol: HTTP 139 | UnhealthyThresholdCount: 2 140 | VpcId: 141 | Fn::ImportValue: !Sub ${EnvironmentName}:VpcId 142 | 143 | # Create a rule on the load balancer for routing traffic to the target group 144 | LoadBalancerRule: 145 | Type: AWS::ElasticLoadBalancingV2::ListenerRule 146 | Properties: 147 | Actions: 148 | - TargetGroupArn: !Ref 'TargetGroup' 149 | Type: 'forward' 150 | Conditions: 151 | - Field: path-pattern 152 | Values: [!Ref 'Path'] 153 | ListenerArn: 154 | Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener 155 | Priority: !Ref 'Priority' 156 | 157 | # Enable autoscaling for this service 158 | ScalableTarget: 159 | Type: AWS::ApplicationAutoScaling::ScalableTarget 160 | DependsOn: Service 161 | Properties: 162 | ServiceNamespace: 'ecs' 163 | ScalableDimension: 'ecs:service:DesiredCount' 164 | ResourceId: 165 | Fn::Join: 166 | - '/' 167 | - - service 168 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 169 | - !Ref 'ServiceName' 170 | MinCapacity: 2 171 | MaxCapacity: 10 172 | RoleARN: 173 | Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole 174 | 175 | # Create scaling policies for the service 176 | ScaleDownPolicy: 177 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 178 | DependsOn: ScalableTarget 179 | Properties: 180 | PolicyName: 181 | Fn::Join: 182 | - '/' 183 | - - scale 184 | - !Ref 'EnvironmentName' 185 | - !Ref 'ServiceName' 186 | - down 187 | PolicyType: StepScaling 188 | ResourceId: 189 | Fn::Join: 190 | - '/' 191 | - - service 192 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 193 | - !Ref 'ServiceName' 194 | ScalableDimension: 'ecs:service:DesiredCount' 195 | ServiceNamespace: 'ecs' 196 | StepScalingPolicyConfiguration: 197 | AdjustmentType: 'ChangeInCapacity' 198 | StepAdjustments: 199 | - MetricIntervalUpperBound: 0 200 | ScalingAdjustment: -1 201 | MetricAggregationType: 'Average' 202 | Cooldown: 60 203 | 204 | ScaleUpPolicy: 205 | Type: AWS::ApplicationAutoScaling::ScalingPolicy 206 | DependsOn: ScalableTarget 207 | Properties: 208 | PolicyName: 209 | Fn::Join: 210 | - '/' 211 | - - scale 212 | - !Ref 'EnvironmentName' 213 | - !Ref 'ServiceName' 214 | - up 215 | PolicyType: StepScaling 216 | ResourceId: 217 | Fn::Join: 218 | - '/' 219 | - - service 220 | - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 221 | - !Ref 'ServiceName' 222 | ScalableDimension: 'ecs:service:DesiredCount' 223 | ServiceNamespace: 'ecs' 224 | StepScalingPolicyConfiguration: 225 | AdjustmentType: 'ChangeInCapacity' 226 | StepAdjustments: 227 | - MetricIntervalLowerBound: 0 228 | MetricIntervalUpperBound: 15 229 | ScalingAdjustment: 1 230 | - MetricIntervalLowerBound: 15 231 | MetricIntervalUpperBound: 25 232 | ScalingAdjustment: 2 233 | - MetricIntervalLowerBound: 25 234 | ScalingAdjustment: 3 235 | MetricAggregationType: 'Average' 236 | Cooldown: 60 237 | 238 | # Create alarms to trigger these policies 239 | LowCpuUsageAlarm: 240 | Type: AWS::CloudWatch::Alarm 241 | Properties: 242 | AlarmName: 243 | Fn::Join: 244 | - '-' 245 | - - low-cpu 246 | - !Ref 'EnvironmentName' 247 | - !Ref 'ServiceName' 248 | AlarmDescription: 249 | Fn::Join: 250 | - ' ' 251 | - - "Low CPU utilization for service" 252 | - !Ref 'ServiceName' 253 | - "in environment" 254 | - !Ref 'EnvironmentName' 255 | MetricName: CPUUtilization 256 | Namespace: AWS/ECS 257 | Dimensions: 258 | - Name: ServiceName 259 | Value: !Ref 'ServiceName' 260 | - Name: ClusterName 261 | Value: 262 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 263 | Statistic: Average 264 | Period: 60 265 | EvaluationPeriods: 1 266 | Threshold: 20 267 | ComparisonOperator: LessThanOrEqualToThreshold 268 | AlarmActions: 269 | - !Ref ScaleDownPolicy 270 | 271 | HighCpuUsageAlarm: 272 | Type: AWS::CloudWatch::Alarm 273 | Properties: 274 | AlarmName: 275 | Fn::Join: 276 | - '-' 277 | - - high-cpu 278 | - !Ref 'EnvironmentName' 279 | - !Ref 'ServiceName' 280 | AlarmDescription: 281 | Fn::Join: 282 | - ' ' 283 | - - "High CPU utilization for service" 284 | - !Ref 'ServiceName' 285 | - "in environment" 286 | - !Ref 'EnvironmentName' 287 | MetricName: CPUUtilization 288 | Namespace: AWS/ECS 289 | Dimensions: 290 | - Name: ServiceName 291 | Value: !Ref 'ServiceName' 292 | - Name: ClusterName 293 | Value: 294 | Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName 295 | Statistic: Average 296 | Period: 60 297 | EvaluationPeriods: 1 298 | Threshold: 70 299 | ComparisonOperator: GreaterThanOrEqualToThreshold 300 | AlarmActions: 301 | - !Ref ScaleUpPolicy 302 | --------------------------------------------------------------------------------