From 9327f8f27ab0a60dd89b54abdefa17152b605372 Mon Sep 17 00:00:00 2001 From: ebosas Date: Mon, 1 Nov 2021 17:14:17 +0200 Subject: [PATCH] Deployment to Amazon ECS/AWS Fargate --- README.md | 2 +- deployments/alb.yml | 80 ++++++ deployments/cluster-ec2.yml | 214 +++++++++++++++ deployments/cluster-fargate.yml | 128 +++++++++ deployments/network.yml | 220 ++++++++++++++++ deployments/resources.yml | 179 +++++++++++++ deployments/services-ec2/cache.yml | 232 ++++++++++++++++ deployments/services-ec2/database.yml | 232 ++++++++++++++++ deployments/services-ec2/server.yml | 294 +++++++++++++++++++++ deployments/services-fargate/cache.yml | 244 +++++++++++++++++ deployments/services-fargate/database.yml | 244 +++++++++++++++++ deployments/services-fargate/server.yml | 307 ++++++++++++++++++++++ 12 files changed, 2375 insertions(+), 1 deletion(-) create mode 100644 deployments/alb.yml create mode 100644 deployments/cluster-ec2.yml create mode 100644 deployments/cluster-fargate.yml create mode 100644 deployments/network.yml create mode 100644 deployments/resources.yml create mode 100644 deployments/services-ec2/cache.yml create mode 100644 deployments/services-ec2/database.yml create mode 100644 deployments/services-ec2/server.yml create mode 100644 deployments/services-fargate/cache.yml create mode 100644 deployments/services-fargate/database.yml create mode 100644 deployments/services-fargate/server.yml diff --git a/README.md b/README.md index c04b480..a97aae6 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ To access the back end service, attach to its docker container from a separate t docker attach microservices_backend ``` -## Deployment on Amazon ECS/AWS Fargate +## Deployment to Amazon ECS/AWS Fargate ### ECR diff --git a/deployments/alb.yml b/deployments/alb.yml new file mode 100644 index 0000000..76156e6 --- /dev/null +++ b/deployments/alb.yml @@ -0,0 +1,80 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: External, public facing load balancer, for forwarding public traffic to containers. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this load balancer to +Resources: + EcsSecurityGroupIngressFromPublicALB: + Type: AWS::EC2::SecurityGroupIngress + Properties: + Description: Ingress from the public ALB + GroupId: + Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + IpProtocol: -1 + SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG' + + # Public load balancer, hosted in public subnets that is accessible + # to the public, and is intended to route traffic to one or more public + # facing services. This is used for accepting traffic from the public + # internet and directing it to public facing microservices + PublicLoadBalancerSG: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Access to the public facing load balancer + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + SecurityGroupIngress: + # Allow access to ALB from anywhere on the internet + - CidrIp: 0.0.0.0/0 + IpProtocol: -1 + PublicLoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + Properties: + Scheme: internet-facing + LoadBalancerAttributes: + - Key: idle_timeout.timeout_seconds + Value: '30' + Subnets: + # The load balancer is placed into the public subnets, so that traffic + # from the internet can reach the load balancer directly via the internet gateway + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo + SecurityGroups: [!Ref 'PublicLoadBalancerSG'] + # A dummy target group is used to setup the ALB to just drop traffic + # initially, before any real service target groups have been added. + DummyTargetGroupPublic: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 6 + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: 2 + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + PublicLoadBalancerListener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - TargetGroupArn: !Ref 'DummyTargetGroupPublic' + Type: 'forward' + LoadBalancerArn: !Ref 'PublicLoadBalancer' + Port: 80 + Protocol: HTTP + +Outputs: + PublicListener: + Description: The ARN of the public load balancer's Listener + Value: !Ref PublicLoadBalancerListener + Export: + Name: !Sub ${EnvironmentName}:PublicListener + ExternalUrl: + Description: The url of the external load balancer + Value: !Sub http://${PublicLoadBalancer.DNSName} + Export: + Name: !Sub ${EnvironmentName}:ExternalUrl diff --git a/deployments/cluster-ec2.yml b/deployments/cluster-ec2.yml new file mode 100644 index 0000000..aac68d8 --- /dev/null +++ b/deployments/cluster-ec2.yml @@ -0,0 +1,214 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: EC2 ECS cluster running containers in public or private subnets. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" + InstanceType: + Type: String + Default: t2.micro + 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 + AllowedValues: [ t2.micro, t2.small, t2.medium, t2.large, t2.xlarge, t2.2xlarge, + m5.large, m5.xlarge, m5.2xlarge, m5.4xlarge, m5.12xlarge, m5.24xlarge, + c5.large, c5.xlarge, c5.2xlarge, c5.4xlarge, c5.9xlarge, c5.18xlarge, + r5.large, r5.xlarge, r5.2xlarge, r5.4xlarge, r5.12xlarge, r5.24xlarge ] + ConstraintDescription: Please choose a valid instance type. + DesiredCapacity: + Type: Number + Default: '1' + Description: Number of EC2 instances to launch in your ECS cluster. + MaxSize: + Type: Number + Default: '3' + Description: Maximum number of EC2 instances that can be launched in your ECS cluster. + ECSAMI: + Type: AWS::SSM::Parameter::Value + Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id + Description: The Amazon Machine Image ID used for the cluster, leave it as the default value to get the latest AMI +Resources: + # ECS Resources + ECSCluster: + Type: AWS::ECS::Cluster + + # Autoscaling group. This launches the actual EC2 instances that will register + # themselves as members of the cluster, and run the docker containers. + ECSAutoScalingGroup: + Type: AWS::AutoScaling::AutoScalingGroup + Properties: + # Protection from scale-in is required with capacity providers but + # the auto scaling group will have to be manually removed when deleting + # the ec2 cluster stack in cloudformation. + NewInstancesProtectedFromScaleIn: true + VPCZoneIdentifier: + # Choose private subnets if using NAT gateways + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo + LaunchConfigurationName: !Ref 'ContainerInstances' + MinSize: '1' + MaxSize: !Ref 'MaxSize' + DesiredCapacity: !Ref 'DesiredCapacity' + CreationPolicy: + ResourceSignal: + Timeout: PT15M + UpdatePolicy: + AutoScalingReplacingUpdate: + WillReplace: true + ContainerInstances: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + ImageId: !Ref 'ECSAMI' + SecurityGroups: + - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + InstanceType: !Ref 'InstanceType' + IamInstanceProfile: !Ref 'EC2InstanceProfile' + UserData: + Fn::Base64: !Sub | + #!/bin/bash -xe + echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config + yum install -y aws-cfn-bootstrap + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ECSAutoScalingGroup --region ${AWS::Region} + EC2InstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: / + Roles: [!Ref 'EC2Role'] + + # Capacity provider. Automatically manages the scaling of the EC2 instances. + ECSCapacityProvider: + Type: AWS::ECS::CapacityProvider + Properties: + AutoScalingGroupProvider: + AutoScalingGroupArn: !Ref ECSAutoScalingGroup + ManagedScaling: + MaximumScalingStepSize: 2 + MinimumScalingStepSize: 1 + Status: ENABLED + TargetCapacity: 100 + ManagedTerminationProtection: ENABLED + ECSClusterCapProvAssoc: + Type: AWS::ECS::ClusterCapacityProviderAssociations + Properties: + Cluster: !Ref ECSCluster + CapacityProviders: + - !Ref ECSCapacityProvider + DefaultCapacityProviderStrategy: + - CapacityProvider: !Ref ECSCapacityProvider + Weight: 100 + + # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets + # on your AWS account + AutoscalingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [application-autoscaling.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: service-autoscaling + PolicyDocument: + Statement: + - Effect: Allow + Action: + - 'application-autoscaling:*' + - 'cloudwatch:DescribeAlarms' + - 'cloudwatch:PutMetricAlarm' + - 'ecs:DescribeServices' + - 'ecs:UpdateService' + Resource: '*' + + # Role for the EC2 hosts. This allows the ECS agent on the EC2 hosts + # to communciate with the ECS control plane, as well as download the docker + # images from ECR to run on your host. + EC2Role: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [ec2.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: ecs-service + PolicyDocument: + Statement: + - Effect: Allow + Action: + - 'ecs:CreateCluster' + - 'ecs:DeregisterContainerInstance' + - 'ecs:DiscoverPollEndpoint' + - 'ecs:Poll' + - 'ecs:RegisterContainerInstance' + - 'ecs:StartTelemetrySession' + - 'ecs:Submit*' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + - 'ecr:GetAuthorizationToken' + - 'ecr:BatchGetImage' + - 'ecr:GetDownloadUrlForLayer' + Resource: '*' + + # This is an IAM role which authorizes ECS to manage resources on your + # account on your behalf, such as updating your load balancer with the + # details of where your containers are, so that traffic can reach your + # containers. + ECSRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [ecs.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: ecs-service + PolicyDocument: + Statement: + - Effect: Allow + Action: + # Rules which allow ECS to attach network interfaces to instances + # on your behalf in order for awsvpc networking mode to work right + - 'ec2:AttachNetworkInterface' + - 'ec2:CreateNetworkInterface' + - 'ec2:CreateNetworkInterfacePermission' + - 'ec2:DeleteNetworkInterface' + - 'ec2:DeleteNetworkInterfacePermission' + - 'ec2:Describe*' + - 'ec2:DetachNetworkInterface' + + # Rules which allow ECS to update load balancers on your behalf + # with the information sabout how to send traffic to your containers + - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' + - 'elasticloadbalancing:DeregisterTargets' + - 'elasticloadbalancing:Describe*' + - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' + - 'elasticloadbalancing:RegisterTargets' + Resource: '*' + +# These are the values output by the CloudFormation template. Be careful +# about changing any of them, because of them are exported with specific +# names so that the other task related CF templates can use them. +Outputs: + ClusterName: + Description: The name of the ECS cluster + Value: !Ref 'ECSCluster' + Export: + Name: !Sub ${EnvironmentName}:ClusterName + AutoscalingRole: + Description: The ARN of the role used for autoscaling + Value: !GetAtt 'AutoscalingRole.Arn' + Export: + Name: !Sub ${EnvironmentName}:AutoscalingRole + ECSRole: + Description: The ARN of the ECS role + Value: !GetAtt 'ECSRole.Arn' + Export: + Name: !Sub ${EnvironmentName}:ECSRole diff --git a/deployments/cluster-fargate.yml b/deployments/cluster-fargate.yml new file mode 100644 index 0000000..d77ca70 --- /dev/null +++ b/deployments/cluster-fargate.yml @@ -0,0 +1,128 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: AWS Fargate cluster that can span public and private subnets. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" +Resources: + # ECS Resources + ECSCluster: + Type: AWS::ECS::Cluster + + # A role used to allow AWS Autoscaling to inspect stats and adjust scaleable targets + # on your AWS account + AutoscalingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [application-autoscaling.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: service-autoscaling + PolicyDocument: + Statement: + - Effect: Allow + Action: + - 'application-autoscaling:*' + - 'cloudwatch:DescribeAlarms' + - 'cloudwatch:PutMetricAlarm' + - 'ecs:DescribeServices' + - 'ecs:UpdateService' + Resource: '*' + + # This is an IAM role which authorizes ECS to manage resources on your + # account on your behalf, such as updating your load balancer with the + # details of where your containers are, so that traffic can reach your + # containers. + ECSRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [ecs.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: ecs-service + PolicyDocument: + Statement: + - Effect: Allow + Action: + # Rules which allow ECS to attach network interfaces to instances + # on your behalf in order for awsvpc networking mode to work right + - 'ec2:AttachNetworkInterface' + - 'ec2:CreateNetworkInterface' + - 'ec2:CreateNetworkInterfacePermission' + - 'ec2:DeleteNetworkInterface' + - 'ec2:DeleteNetworkInterfacePermission' + - 'ec2:Describe*' + - 'ec2:DetachNetworkInterface' + + # Rules which allow ECS to update load balancers on your behalf + # with the information sabout how to send traffic to your containers + - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' + - 'elasticloadbalancing:DeregisterTargets' + - 'elasticloadbalancing:Describe*' + - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' + - 'elasticloadbalancing:RegisterTargets' + Resource: '*' + + # This is a role which is used by the ECS tasks themselves. + ECSTaskExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Principal: + Service: [ecs-tasks.amazonaws.com] + Action: ['sts:AssumeRole'] + Path: / + Policies: + - PolicyName: AmazonECSTaskExecutionRolePolicy + PolicyDocument: + Statement: + - Effect: Allow + Action: + # Allow the ECS Tasks to download images from ECR + - 'ecr:GetAuthorizationToken' + - 'ecr:BatchCheckLayerAvailability' + - 'ecr:GetDownloadUrlForLayer' + - 'ecr:BatchGetImage' + + # Allow the ECS tasks to upload logs to CloudWatch + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + Resource: '*' + +# These are the values output by the CloudFormation template. Be careful +# about changing any of them, because of them are exported with specific +# names so that the other task related CF templates can use them. +Outputs: + ClusterName: + Description: The name of the ECS cluster + Value: !Ref 'ECSCluster' + Export: + Name: !Sub ${EnvironmentName}:ClusterName + AutoscalingRole: + Description: The ARN of the role used for autoscaling + Value: !GetAtt 'AutoscalingRole.Arn' + Export: + Name: !Sub ${EnvironmentName}:AutoscalingRole + ECSRole: + Description: The ARN of the ECS role + Value: !GetAtt 'ECSRole.Arn' + Export: + Name: !Sub ${EnvironmentName}:ECSRole + ECSTaskExecutionRole: + Description: The ARN of the ECS role + Value: !GetAtt 'ECSTaskExecutionRole.Arn' + Export: + Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole diff --git a/deployments/network.yml b/deployments/network.yml new file mode 100644 index 0000000..508e851 --- /dev/null +++ b/deployments/network.yml @@ -0,0 +1,220 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: The network stack for the ECS cluster. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" + +Mappings: + # Hard values for the subnet masks. These masks define + # the range of internal IP addresses that can be assigned. + # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 + # There are four subnets which cover the ranges: + # + # 10.0.0.0 - 10.0.0.255 + # 10.0.1.0 - 10.0.1.255 + # 10.0.2.0 - 10.0.2.255 + # 10.0.3.0 - 10.0.3.255 + # + # If you need more IP addresses (perhaps you have so many + # instances that you run out) then you can customize these + # ranges to add more + SubnetConfig: + VPC: + CIDR: '10.0.0.0/16' + PublicOne: + CIDR: '10.0.0.0/24' + PublicTwo: + CIDR: '10.0.1.0/24' + PrivateOne: + CIDR: '10.0.2.0/24' + PrivateTwo: + CIDR: '10.0.3.0/24' + +Resources: + # VPC in which containers will be networked. + # It has two public subnets, and two private subnets. + # We distribute the subnets across the first two available subnets + # for the region, for high availability. + VPC: + Type: AWS::EC2::VPC + Properties: + EnableDnsSupport: true + EnableDnsHostnames: true + CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] + + # Two public subnets, where containers can have public IP addresses + PublicSubnetOne: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: {Ref: 'AWS::Region'} + VpcId: !Ref 'VPC' + CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] + MapPublicIpOnLaunch: true + PublicSubnetTwo: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: {Ref: 'AWS::Region'} + VpcId: !Ref 'VPC' + CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] + MapPublicIpOnLaunch: true + + # Two private subnets where containers will only have private + # IP addresses, and will only be reachable by other members of the + # VPC + PrivateSubnetOne: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: {Ref: 'AWS::Region'} + VpcId: !Ref 'VPC' + CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR'] + PrivateSubnetTwo: + Type: AWS::EC2::Subnet + Properties: + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: {Ref: 'AWS::Region'} + VpcId: !Ref 'VPC' + CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR'] + + # Setup networking resources for the public subnets. Containers + # in the public subnets have public IP addresses and the routing table + # sends network traffic via the internet gateway. + InternetGateway: + Type: AWS::EC2::InternetGateway + GatewayAttachement: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: !Ref 'VPC' + InternetGatewayId: !Ref 'InternetGateway' + PublicRouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + PublicRoute: + Type: AWS::EC2::Route + DependsOn: GatewayAttachement + Properties: + RouteTableId: !Ref 'PublicRouteTable' + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: !Ref 'InternetGateway' + PublicSubnetOneRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetOne + RouteTableId: !Ref PublicRouteTable + PublicSubnetTwoRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: !Ref PublicSubnetTwo + RouteTableId: !Ref PublicRouteTable + + # Setup networking resources for the private subnets. Containers + # in these subnets have only private IP addresses, and must use a NAT + # gateway to talk to the internet. We launch two NAT gateways, one for + # each private subnet. + # NatGatewayOneAttachment: + # Type: AWS::EC2::EIP + # DependsOn: GatewayAttachement + # Properties: + # Domain: vpc + # NatGatewayTwoAttachment: + # Type: AWS::EC2::EIP + # DependsOn: GatewayAttachement + # Properties: + # Domain: vpc + # NatGatewayOne: + # Type: AWS::EC2::NatGateway + # Properties: + # AllocationId: !GetAtt NatGatewayOneAttachment.AllocationId + # SubnetId: !Ref PublicSubnetOne + # NatGatewayTwo: + # Type: AWS::EC2::NatGateway + # Properties: + # AllocationId: !GetAtt NatGatewayTwoAttachment.AllocationId + # SubnetId: !Ref PublicSubnetTwo + PrivateRouteTableOne: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + # PrivateRouteOne: + # Type: AWS::EC2::Route + # Properties: + # RouteTableId: !Ref PrivateRouteTableOne + # DestinationCidrBlock: 0.0.0.0/0 + # NatGatewayId: !Ref NatGatewayOne + PrivateRouteTableOneAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTableOne + SubnetId: !Ref PrivateSubnetOne + PrivateRouteTableTwo: + Type: AWS::EC2::RouteTable + Properties: + VpcId: !Ref 'VPC' + # PrivateRouteTwo: + # Type: AWS::EC2::Route + # Properties: + # RouteTableId: !Ref PrivateRouteTableTwo + # DestinationCidrBlock: 0.0.0.0/0 + # NatGatewayId: !Ref NatGatewayTwo + PrivateRouteTableTwoAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + RouteTableId: !Ref PrivateRouteTableTwo + SubnetId: !Ref PrivateSubnetTwo + + # A security group for either the EC2 hosts that will run the containers + # or the containers we will run in Fargate. Rules are added based on + # what ingress you choose to add to the cluster. + ContainerSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Access to the ECS hosts that run containers + VpcId: !Ref 'VPC' + +# These are the values output by the CloudFormation template. Be careful +# about changing any of them, because of them are exported with specific +# names so that the other task related CF templates can use them. +Outputs: + VpcId: + Description: The ID of the VPC that this stack is deployed in + Value: !Ref 'VPC' + Export: + Name: !Sub ${EnvironmentName}:VpcId + PublicSubnetOne: + Description: Public subnet one + Value: !Ref 'PublicSubnetOne' + Export: + Name: !Sub ${EnvironmentName}:PublicSubnetOne + PublicSubnetTwo: + Description: Public subnet two + Value: !Ref 'PublicSubnetTwo' + Export: + Name: !Sub ${EnvironmentName}:PublicSubnetTwo + PrivateSubnetOne: + Description: Private subnet one + Value: !Ref 'PrivateSubnetOne' + Export: + Name: !Sub ${EnvironmentName}:PrivateSubnetOne + PrivateSubnetTwo: + Description: Private subnet two + Value: !Ref 'PrivateSubnetTwo' + Export: + Name: !Sub ${EnvironmentName}:PrivateSubnetTwo + ContainerSecurityGroup: + Description: A security group used to allow containers to receive traffic + Value: !Ref 'ContainerSecurityGroup' + Export: + Name: !Sub ${EnvironmentName}:ContainerSecurityGroup diff --git a/deployments/resources.yml b/deployments/resources.yml new file mode 100644 index 0000000..7af4c1a --- /dev/null +++ b/deployments/resources.yml @@ -0,0 +1,179 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: The RabbitMQ, Redis, and PostgreSQL resources. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: "A friendly environment name that will be used for namespacing all cluster resources. Example: staging, qa, or production" + RabbitUsername: + Type: String + Default: rabbit + Description: A RabbitMQ username + PostgresUsername: + Type: String + Default: postgres + Description: A Postgres username + RabbitPassword: + NoEcho: true + Type: String + Default: Secret123456 # remove + AllowedPattern: "^[a-zA-Z0-9]{12,20}$" + Description: The RabbitMQ password + PostgresPassword: + NoEcho: true + Type: String + Default: Secret123456 # remove + AllowedPattern: "^[a-zA-Z0-9]{12,20}$" + Description: The Postgres password +Resources: + # A RabbitMQ broker + RabbitMQ: + Type: AWS::AmazonMQ::Broker + Properties: + AutoMinorVersionUpgrade: false + BrokerName: RabbitBroker + DeploymentMode: SINGLE_INSTANCE + EngineType: RABBITMQ + EngineVersion: 3.8.22 + HostInstanceType: mq.t3.micro + PubliclyAccessible: false + SecurityGroups: [!Ref RabbitSecurityGroup] + SubnetIds: + - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne + Users: + - Password: !Ref RabbitPassword + Username: !Ref RabbitUsername + RabbitSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + GroupDescription: Access to RabbitMQ + SecurityGroupIngress: + - SourceSecurityGroupId: + Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + IpProtocol: tcp + FromPort: 5671 + ToPort: 5671 + + # A Redis cluster + Redis: + Type: AWS::ElastiCache::CacheCluster + DeletionPolicy: Delete + Properties: + Engine: redis + ClusterName: RedisCluster + CacheNodeType: cache.t2.micro + NumCacheNodes: 1 + CacheSubnetGroupName: !Ref RedisSubnetGroup + VpcSecurityGroupIds: [!Ref RedisSecurityGroup] + RedisSubnetGroup: + Type: AWS::ElastiCache::SubnetGroup + Properties: + Description: !Sub ${EnvironmentName}-Redis + SubnetIds: + - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo + RedisSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + GroupDescription: Access to Redis + SecurityGroupIngress: + - SourceSecurityGroupId: + Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + IpProtocol: tcp + FromPort: 6379 + ToPort: 6379 + + # A Postgres database + Postgres: + Type: AWS::RDS::DBInstance + DeletionPolicy: Delete + Properties: + DBInstanceIdentifier: postgresinstance + DBName: microservices + DBInstanceClass: db.t2.micro + StorageType: gp2 + AllocatedStorage: 20 + MaxAllocatedStorage: 21 + Engine: postgres + EngineVersion: 12.8 + MasterUsername: !Ref PostgresUsername + MasterUserPassword: !Ref PostgresPassword + MultiAZ: false + PubliclyAccessible: false + VPCSecurityGroups: [!Ref PostgresSecurityGroup] + DBSubnetGroupName: !Ref PostgresSubnetGroup + PostgresSubnetGroup: + Type: AWS::RDS::DBSubnetGroup + Properties: + DBSubnetGroupDescription: !Sub ${EnvironmentName}-Postgres + SubnetIds: + - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PrivateSubnetTwo + PostgresSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + GroupDescription: Access to Postgres + SecurityGroupIngress: + - SourceSecurityGroupId: + Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + IpProtocol: tcp + FromPort: 5432 + ToPort: 5432 + # - CidrIp: 0.0.0.0/0 + # IpProtocol: tcp + # FromPort: 5432 + # ToPort: 5432 + + # # An example showing how to use Secrets Manager to generate login credentials. + # # Refer in templates like this '{{resolve:secretsmanager:RabbitSecrets::password}}' + # RabbitSecrets: + # Type: AWS::SecretsManager::Secret + # Properties: + # Name: RabbitSecrets + # Description: This secret has a dynamically generated password + # GenerateSecretString: + # SecretStringTemplate: '{"username": "rabbit"}' + # GenerateStringKey: "password" + # PasswordLength: 15 + # ExcludeCharacters: ',:=' + + # Connection strings for the resources created in this stack, will be passed to + # services as environmental variables. This will expose passwords in SSM Parameter + # Store as well as the ECS tasks definitions interface. Instead, use Secrets + # Manager to generate passwords and retrieve directly in applicaton code as shown + # in the commented example above. + RabbitURLParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /microservices/${EnvironmentName}/rabbiturl + Type: String + Description: A connection string for RabbitMQ + Value: + Fn::Join: + - '' + - - !Sub amqps://${RabbitUsername}:${RabbitPassword}@ + - !Select [1, !Split ['://', !Select [0, !GetAtt RabbitMQ.AmqpEndpoints]]] + PostgresURLParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /microservices/${EnvironmentName}/postgresurl + Type: String + Description: A connection string for Postgres + Value: + Fn::Join: + - '' + - - !Sub postgres://${PostgresUsername}:${PostgresPassword}@ + - !Sub ${Postgres.Endpoint.Address}:${Postgres.Endpoint.Port} + RedisURLParameter: + Type: AWS::SSM::Parameter + Properties: + Name: !Sub /microservices/${EnvironmentName}/redisurl + Type: String + Description: A connection string for Redis + Value: !Sub ${Redis.RedisEndpoint.Address}:${Redis.RedisEndpoint.Port} diff --git a/deployments/services-ec2/cache.yml b/deployments/services-ec2/cache.yml new file mode 100644 index 0000000..15826ed --- /dev/null +++ b/deployments/services-ec2/cache.yml @@ -0,0 +1,232 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service into an ECS cluster behind a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: cache + Description: A name for the service + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 230 + Description: How much memory in megabytes to give the container + DesiredCount: + Type: Number + Default: 1 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: REDIS_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/redisurl}}' + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + TaskDefinition: !Ref 'TaskDefinition' + PlacementStrategies: + - Field: memory + Type: binpack + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy diff --git a/deployments/services-ec2/database.yml b/deployments/services-ec2/database.yml new file mode 100644 index 0000000..315a00a --- /dev/null +++ b/deployments/services-ec2/database.yml @@ -0,0 +1,232 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service into an ECS cluster behind a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: database + Description: A name for the service + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 230 + Description: How much memory in megabytes to give the container + DesiredCount: + Type: Number + Default: 1 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: POSTGRES_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/postgresurl}}' + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + TaskDefinition: !Ref 'TaskDefinition' + PlacementStrategies: + - Field: memory + Type: binpack + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy diff --git a/deployments/services-ec2/server.yml b/deployments/services-ec2/server.yml new file mode 100644 index 0000000..64800f4 --- /dev/null +++ b/deployments/services-ec2/server.yml @@ -0,0 +1,294 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service into an ECS cluster behind a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: server + Description: A name for the service + ContainerPort: + Type: Number + Default: 80 + Description: What port number the application inside the docker container is binding to + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 230 + Description: How much memory in megabytes to give the container + Path: + Type: String + Default: "*" + Description: A path on the public load balancer that this service + should be connected to. Use * to send all load balancer + traffic to this service. + Priority: + Type: Number + Default: 1 + Description: The priority for the routing rule added to the load balancer. + This only applies if your have multiple services which have been + assigned to different paths on the load balancer. + DesiredCount: + Type: Number + Default: 1 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + PortMappings: + - ContainerPort: !Ref 'ContainerPort' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: REDIS_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/redisurl}}' + - Name: SERVER_ADDR + Value: 0.0.0.0:80 + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + DependsOn: LoadBalancerRule + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + TaskDefinition: !Ref 'TaskDefinition' + PlacementStrategies: + - Field: memory + Type: binpack + LoadBalancers: + - ContainerName: !Ref 'ServiceName' + ContainerPort: !Ref 'ContainerPort' + TargetGroupArn: !Ref 'TargetGroup' + + # A target group. This is used for keeping track of all the tasks, and + # what IP addresses / port numbers they have. You can query it yourself, + # to use the addresses yourself, but most often this target group is just + # connected to an application load balancer, or network load balancer, so + # it can automatically distribute traffic across all the targets. + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 6 + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + Name: !Ref 'ServiceName' + Port: 80 + Protocol: HTTP + UnhealthyThresholdCount: 2 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + + # Create a rule on the load balancer for routing traffic to the target group + LoadBalancerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref 'TargetGroup' + Type: 'forward' + Conditions: + - Field: path-pattern + Values: [!Ref 'Path'] + ListenerArn: + Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener + Priority: !Ref 'Priority' + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy diff --git a/deployments/services-fargate/cache.yml b/deployments/services-fargate/cache.yml new file mode 100644 index 0000000..53a60cf --- /dev/null +++ b/deployments/services-fargate/cache.yml @@ -0,0 +1,244 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service on AWS Fargate, accessible via a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: cache + Description: A name for the service + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 512 + Description: How much memory in megabytes to give the container + DesiredCount: + Type: Number + Default: 2 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + ExecutionRoleArn: + Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: REDIS_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/redisurl}}' + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + LaunchType: FARGATE + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + Subnets: + # Choose private subnets if using NAT gateways + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo + TaskDefinition: !Ref 'TaskDefinition' + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy diff --git a/deployments/services-fargate/database.yml b/deployments/services-fargate/database.yml new file mode 100644 index 0000000..837bd10 --- /dev/null +++ b/deployments/services-fargate/database.yml @@ -0,0 +1,244 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service on AWS Fargate, accessible via a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: database + Description: A name for the service + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 512 + Description: How much memory in megabytes to give the container + DesiredCount: + Type: Number + Default: 2 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + ExecutionRoleArn: + Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: POSTGRES_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/postgresurl}}' + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + LaunchType: FARGATE + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + Subnets: + # Choose private subnets if using NAT gateways + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo + TaskDefinition: !Ref 'TaskDefinition' + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy diff --git a/deployments/services-fargate/server.yml b/deployments/services-fargate/server.yml new file mode 100644 index 0000000..e0edab7 --- /dev/null +++ b/deployments/services-fargate/server.yml @@ -0,0 +1,307 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Deploy a service on AWS Fargate, accessible via a public load balancer. +Parameters: + EnvironmentName: + Type: String + Default: production + Description: The name of the environment to add this service to + ServiceName: + Type: String + Default: server + Description: A name for the service + ContainerPort: + Type: Number + Default: 80 + Description: What port number the application inside the docker container is binding to + ContainerCpu: + Type: Number + Default: 256 + Description: How much CPU to give the container. 1024 is 1 CPU + ContainerMemory: + Type: Number + Default: 512 + Description: How much memory in megabytes to give the container + Path: + Type: String + Default: "*" + Description: A path on the load balancer that this service + should be connected to. Use * to send all load balancer + traffic to this service. + Priority: + Type: Number + Default: 1 + Description: The priority for the routing rule added to the load balancer. + This only applies if your have multiple services which have been + assigned to different paths on the load balancer. + DesiredCount: + Type: Number + Default: 2 + Description: How many copies of the service task to run + Role: + Type: String + Default: "" + Description: (Optional) An IAM role to give the service's containers if the code within needs to + access other AWS resources like S3 buckets, DynamoDB tables, etc + +Conditions: + HasCustomRole: !Not [ !Equals [!Ref 'Role', ''] ] + +Resources: + # A log group for storing the stdout logs from this service's containers + LogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName} + + # The task definition. This is a simple metadata description of what + # container to run, and what resource requirements it has. + TaskDefinition: + Type: AWS::ECS::TaskDefinition + Properties: + Family: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + NetworkMode: awsvpc + RequiresCompatibilities: + - FARGATE + ExecutionRoleArn: + Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole + TaskRoleArn: + Fn::If: + - 'HasCustomRole' + - !Ref 'Role' + - !Ref "AWS::NoValue" + ContainerDefinitions: + - Name: !Ref 'ServiceName' + Cpu: !Ref 'ContainerCpu' + Memory: !Ref 'ContainerMemory' + Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/microservices/${ServiceName}:latest + PortMappings: + - ContainerPort: !Ref 'ContainerPort' + LogConfiguration: + LogDriver: 'awslogs' + Options: + awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName} + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: !Ref 'ServiceName' + Environment: + - Name: RABBIT_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/rabbiturl}}' + - Name: REDIS_URL + Value: !Sub '{{resolve:ssm:/microservices/${EnvironmentName}/redisurl}}' + - Name: SERVER_ADDR + Value: 0.0.0.0:80 + + # The service. The service is a resource which allows you to run multiple + # copies of a type of task, and gather up their logs and metrics, as well + # as monitor the number of running tasks and replace any that have crashed + Service: + Type: AWS::ECS::Service + DependsOn: LoadBalancerRule + Properties: + ServiceName: !Ref 'ServiceName' + Cluster: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + LaunchType: FARGATE + DeploymentConfiguration: + MaximumPercent: 200 + MinimumHealthyPercent: 75 + DesiredCount: !Ref 'DesiredCount' + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: + - Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup + Subnets: + # Choose private subnets if using NAT gateways + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne + - Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo + TaskDefinition: !Ref 'TaskDefinition' + LoadBalancers: + - ContainerName: !Ref 'ServiceName' + ContainerPort: !Ref 'ContainerPort' + TargetGroupArn: !Ref 'TargetGroup' + + # A target group. This is used for keeping track of all the tasks, and + # what IP addresses / port numbers they have. You can query it yourself, + # to use the addresses yourself, but most often this target group is just + # connected to an application load balancer, or network load balancer, so + # it can automatically distribute traffic across all the targets. + TargetGroup: + Type: AWS::ElasticLoadBalancingV2::TargetGroup + Properties: + HealthCheckIntervalSeconds: 6 + HealthCheckPath: / + HealthCheckProtocol: HTTP + HealthCheckTimeoutSeconds: 5 + HealthyThresholdCount: 2 + TargetType: ip + Name: !Ref 'ServiceName' + Port: !Ref 'ContainerPort' + Protocol: HTTP + UnhealthyThresholdCount: 2 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: 60 + VpcId: + Fn::ImportValue: !Sub ${EnvironmentName}:VpcId + + # Create a rule on the load balancer for routing traffic to the target group + LoadBalancerRule: + Type: AWS::ElasticLoadBalancingV2::ListenerRule + Properties: + Actions: + - TargetGroupArn: !Ref 'TargetGroup' + Type: 'forward' + Conditions: + - Field: path-pattern + Values: [!Ref 'Path'] + ListenerArn: + Fn::ImportValue: !Sub ${EnvironmentName}:PublicListener + Priority: !Ref 'Priority' + + # Enable autoscaling for this service + ScalableTarget: + Type: AWS::ApplicationAutoScaling::ScalableTarget + DependsOn: Service + Properties: + ServiceNamespace: 'ecs' + ScalableDimension: 'ecs:service:DesiredCount' + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + MinCapacity: 1 + MaxCapacity: 10 + RoleARN: + Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole + + # Create scaling policies for the service + ScaleDownPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - down + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalUpperBound: 0 + ScalingAdjustment: -1 + MetricAggregationType: 'Average' + Cooldown: 60 + + ScaleUpPolicy: + Type: AWS::ApplicationAutoScaling::ScalingPolicy + DependsOn: ScalableTarget + Properties: + PolicyName: + Fn::Join: + - '/' + - - scale + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + - up + PolicyType: StepScaling + ResourceId: + Fn::Join: + - '/' + - - service + - Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + - !Ref 'ServiceName' + ScalableDimension: 'ecs:service:DesiredCount' + ServiceNamespace: 'ecs' + StepScalingPolicyConfiguration: + AdjustmentType: 'ChangeInCapacity' + StepAdjustments: + - MetricIntervalLowerBound: 0 + MetricIntervalUpperBound: 15 + ScalingAdjustment: 1 + - MetricIntervalLowerBound: 15 + MetricIntervalUpperBound: 25 + ScalingAdjustment: 2 + - MetricIntervalLowerBound: 25 + ScalingAdjustment: 3 + MetricAggregationType: 'Average' + Cooldown: 60 + + # Create alarms to trigger these policies + LowCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - low-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "Low CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 20 + ComparisonOperator: LessThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleDownPolicy + + HighCpuUsageAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmName: + Fn::Join: + - '-' + - - high-cpu + - !Ref 'EnvironmentName' + - !Ref 'ServiceName' + AlarmDescription: + Fn::Join: + - ' ' + - - "High CPU utilization for service" + - !Ref 'ServiceName' + - "in environment" + - !Ref 'EnvironmentName' + MetricName: CPUUtilization + Namespace: AWS/ECS + Dimensions: + - Name: ServiceName + Value: !Ref 'ServiceName' + - Name: ClusterName + Value: + Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName + Statistic: Average + Period: 60 + EvaluationPeriods: 1 + Threshold: 70 + ComparisonOperator: GreaterThanOrEqualToThreshold + AlarmActions: + - !Ref ScaleUpPolicy