Created
February 17, 2019 15:41
-
-
Save richardsonlima/3635ec643f9a5b3fcfa1844ac5d678e2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
# Copyright 2018 widdix GmbH | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
AWSTemplateFormatVersion: '2010-09-09' | |
Description: 'ECS: service that runs on an ECS cluster based on ecs/cluster.yaml and uses a dedicated ALB, a cloudonaut.io template' | |
Metadata: | |
'AWS::CloudFormation::Interface': | |
ParameterGroups: | |
- Label: | |
default: 'Parent Stacks' | |
Parameters: | |
- ParentVPCStack | |
- ParentClusterStack | |
- ParentAuthProxyStack | |
- ParentAlertStack | |
- ParentZoneStack | |
- ParentS3StackAccessLog | |
- Label: | |
default: 'Load Balancer Parameters' | |
Parameters: | |
- LoadBalancerScheme | |
- LoadBalancerCertificateArn | |
- LoadBalancerIdleTimeout | |
- LoadBalancerDeregistrationDelay | |
- Label: | |
default: 'Task Parameters' | |
Parameters: | |
- Image | |
- Label: | |
default: 'Service Parameters' | |
Parameters: | |
- SubDomainNameWithDot | |
- DesiredCount | |
- AutoScaling | |
- MaxCapacity | |
- MinCapacity | |
- HealthCheckGracePeriod | |
Parameters: | |
ParentVPCStack: | |
Description: 'Stack name of parent VPC stack based on vpc/vpc-*azs.yaml template.' | |
Type: String | |
ParentClusterStack: | |
Description: 'Stack name of parent Cluster stack based on ecs/cluster.yaml template.' | |
Type: String | |
ParentAuthProxyStack: | |
Description: 'Optional stack name of parent auth proxy stack based on security/auth-proxy-*.yaml template.' | |
Type: String | |
Default: '' | |
ParentAlertStack: | |
Description: 'Optional but recommended stack name of parent alert stack based on operations/alert.yaml template.' | |
Type: String | |
Default: '' | |
ParentZoneStack: | |
Description: 'Optional stack name of parent zone stack based on vpc/zone-*.yaml template.' | |
Type: String | |
Default: '' | |
ParentS3StackAccessLog: | |
Description: 'Optional stack name of parent s3 stack based on state/s3.yaml template (with Access set to ElbAccessLogWrite) to store access logs.' | |
Type: String | |
Default: '' | |
LoadBalancerScheme: | |
Description: 'Indicates whether the load balancer in front of the ECS service is internet-facing or internal.' | |
Type: String | |
Default: 'internet-facing' | |
AllowedValues: | |
- 'internet-facing' | |
- internal | |
LoadBalancerCertificateArn: | |
Description: 'Optional Amazon Resource Name (ARN) of the certificate to associate with the load balancer.' | |
Type: String | |
Default: '' | |
LoadBalancerIdleTimeout: | |
Description: 'The idle timeout value, in seconds.' | |
Type: Number | |
Default: 60 | |
MinValue: 1 | |
MaxValue: 4000 | |
LoadBalancerDeregistrationDelay: | |
Description: 'The amount time (in seconds) to wait before changing the state of a deregistering target from draining to unused.' | |
Type: Number | |
Default: 60 | |
ConstraintDescription: 'Must be in the range [0-3600]' | |
MinValue: 0 | |
MaxValue: 3600 | |
Image: | |
Description: 'The image to use for a container, which is passed directly to the Docker daemon. You can use images in the Docker Hub registry or specify other repositories (repository-url/image:tag).' | |
Type: String | |
DesiredCount: | |
Description: 'The number of simultaneous tasks, which you specify by using the TaskDefinition property, that you want to run on the cluster.' | |
Type: Number | |
Default: 2 | |
ConstraintDescription: 'Must be >= 1' | |
MinValue: 1 | |
MaxCapacity: | |
Description: 'The maximum number of simultaneous tasks, that you want to run on the cluster.' | |
Type: Number | |
Default: 4 | |
ConstraintDescription: 'Must be >= 1' | |
MinValue: 1 | |
MinCapacity: | |
Description: 'The minimum number of simultaneous tasks, that you want to run on the cluster.' | |
Type: Number | |
Default: 2 | |
ConstraintDescription: 'Must be >= 1' | |
MinValue: 1 | |
SubDomainNameWithDot: | |
Description: 'Name that is used to create the DNS entry with trailing dot, e.g. §{SubDomainNameWithDot}§{HostedZoneName}. Leave blank for naked (or apex and bare) domain. Requires ParentZoneStack parameter!' | |
Type: String | |
Default: '' | |
AutoScaling: | |
Description: 'Scale number of tasks based on CPU load?' | |
Type: String | |
Default: 'true' | |
AllowedValues: ['true', 'false'] | |
HealthCheckGracePeriod: | |
Description: 'The period of time, in seconds, that the Amazon ECS service scheduler ignores unhealthy Elastic Load Balancing target health checks after a task has first started.' | |
Type: Number | |
Default: 60 | |
MinValue: 0 | |
MaxValue: 1800 | |
Conditions: | |
HasAuthProxySecurityGroup: !Not [!Equals [!Ref ParentAuthProxyStack, '']] | |
HasNotAuthProxySecurityGroup: !Equals [!Ref ParentAuthProxyStack, ''] | |
HasLoadBalancerSchemeInternal: !Equals [!Ref LoadBalancerScheme, 'internal'] | |
HasLoadBalancerCertificateArn: !Not [!Equals [!Ref LoadBalancerCertificateArn, '']] | |
HasAuthProxySecurityGroupAndLoadBalancerCertificateArn: !And [!Condition HasAuthProxySecurityGroup, !Condition HasLoadBalancerCertificateArn] | |
HasNotAuthProxySecurityGroupAndLoadBalancerCertificateArn: !And [!Condition HasNotAuthProxySecurityGroup, !Condition HasLoadBalancerCertificateArn] | |
HasAlertTopic: !Not [!Equals [!Ref ParentAlertStack, '']] | |
HasZone: !Not [!Equals [!Ref ParentZoneStack, '']] | |
HasS3Bucket: !Not [!Equals [!Ref ParentS3StackAccessLog, '']] | |
HasAutoScaling: !Equals [!Ref AutoScaling, 'true'] | |
Resources: | |
TaskDefinition: | |
Type: 'AWS::ECS::TaskDefinition' | |
Properties: | |
Family: !Ref 'AWS::StackName' | |
NetworkMode: bridge | |
ContainerDefinitions: | |
- Name: main # if you change this, you also must change the AWS::ECS::Service | |
Image: !Ref Image | |
Memory: 128 | |
PortMappings: | |
- ContainerPort: 80 # if you change this, you also must change the AWS::ECS::Service | |
Protocol: tcp | |
Essential: true | |
LogConfiguration: | |
LogDriver: awslogs | |
Options: | |
'awslogs-region': !Ref 'AWS::Region' | |
'awslogs-group': {'Fn::ImportValue': !Sub '${ParentClusterStack}-LogGroup'} | |
'awslogs-stream-prefix': !Ref 'AWS::StackName' | |
ALBSecurityGroup: | |
Type: 'AWS::EC2::SecurityGroup' | |
Properties: | |
GroupDescription: 'ecs-cluster-alb' | |
VpcId: {'Fn::ImportValue': !Sub '${ParentVPCStack}-VPC'} | |
ALBSecurityGroupInHttpWorld: | |
Type: 'AWS::EC2::SecurityGroupIngress' | |
Condition: HasNotAuthProxySecurityGroup | |
Properties: | |
GroupId: !Ref ALBSecurityGroup | |
IpProtocol: tcp | |
FromPort: 80 | |
ToPort: 80 | |
CidrIp: '0.0.0.0/0' | |
ALBSecurityGroupInHttpsWorld: | |
Type: 'AWS::EC2::SecurityGroupIngress' | |
Condition: HasNotAuthProxySecurityGroupAndLoadBalancerCertificateArn | |
Properties: | |
GroupId: !Ref ALBSecurityGroup | |
IpProtocol: tcp | |
FromPort: 443 | |
ToPort: 443 | |
CidrIp: '0.0.0.0/0' | |
ALBSecurityGroupInHttpAuthProxy: | |
Type: 'AWS::EC2::SecurityGroupIngress' | |
Condition: HasAuthProxySecurityGroup | |
Properties: | |
GroupId: !Ref ALBSecurityGroup | |
IpProtocol: tcp | |
FromPort: 80 | |
ToPort: 80 | |
SourceSecurityGroupId: {'Fn::ImportValue': !Sub '${ParentAuthProxyStack}-SecurityGroup'} | |
ALBSecurityGroupInHttpsAuthProxy: | |
Type: 'AWS::EC2::SecurityGroupIngress' | |
Condition: HasAuthProxySecurityGroupAndLoadBalancerCertificateArn | |
Properties: | |
GroupId: !Ref ALBSecurityGroup | |
IpProtocol: tcp | |
FromPort: 443 | |
ToPort: 443 | |
SourceSecurityGroupId: {'Fn::ImportValue': !Sub '${ParentAuthProxyStack}-SecurityGroup'} | |
SecurityGroupInALB: | |
Type: 'AWS::EC2::SecurityGroupIngress' | |
Properties: | |
GroupId: {'Fn::ImportValue': !Sub '${ParentClusterStack}-SecurityGroup'} | |
IpProtocol: tcp | |
FromPort: 0 | |
ToPort: 65535 | |
SourceSecurityGroupId: !Ref ALBSecurityGroup | |
DefaultTargetGroup: # not monitored, but LoadBalancer is monitored! | |
Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' | |
Properties: | |
HealthCheckIntervalSeconds: 15 | |
HealthCheckPath: '/' | |
HealthCheckProtocol: HTTP | |
HealthCheckTimeoutSeconds: 10 | |
HealthyThresholdCount: 2 | |
UnhealthyThresholdCount: 2 | |
Matcher: | |
HttpCode: '200-299' | |
Port: 80 | |
Protocol: HTTP | |
VpcId: {'Fn::ImportValue': !Sub '${ParentVPCStack}-VPC'} | |
TargetGroupAttributes: | |
- Key: deregistration_delay.timeout_seconds | |
Value: !Ref LoadBalancerDeregistrationDelay | |
HTTPCodeELB5XXTooHighAlarm: | |
Condition: HasAlertTopic | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Application load balancer returns 5XX HTTP status codes' | |
Namespace: 'AWS/ApplicationELB' | |
MetricName: HTTPCode_ELB_5XX_Count | |
Statistic: Sum | |
Period: 60 | |
EvaluationPeriods: 1 | |
ComparisonOperator: GreaterThanThreshold | |
Threshold: 0 | |
AlarmActions: | |
- {'Fn::ImportValue': !Sub '${ParentAlertStack}-TopicARN'} | |
Dimensions: | |
- Name: LoadBalancer | |
Value: !GetAtt LoadBalancer.LoadBalancerFullName | |
HTTPCodeTarget5XXTooHighAlarm: | |
Condition: HasAlertTopic | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Application load balancer receives 5XX HTTP status codes from targets' | |
Namespace: 'AWS/ApplicationELB' | |
MetricName: HTTPCode_Target_5XX_Count | |
Statistic: Sum | |
Period: 60 | |
EvaluationPeriods: 1 | |
ComparisonOperator: GreaterThanThreshold | |
Threshold: 0 | |
AlarmActions: | |
- {'Fn::ImportValue': !Sub '${ParentAlertStack}-TopicARN'} | |
Dimensions: | |
- Name: LoadBalancer | |
Value: !GetAtt LoadBalancer.LoadBalancerFullName | |
RejectedConnectionCountTooHighAlarm: | |
Condition: HasAlertTopic | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Application load balancer rejected connections because the load balancer had reached its maximum number of connections' | |
Namespace: 'AWS/ApplicationELB' | |
MetricName: RejectedConnectionCount | |
Statistic: Sum | |
Period: 60 | |
EvaluationPeriods: 1 | |
ComparisonOperator: GreaterThanThreshold | |
Threshold: 0 | |
AlarmActions: | |
- {'Fn::ImportValue': !Sub '${ParentAlertStack}-TopicARN'} | |
Dimensions: | |
- Name: LoadBalancer | |
Value: !GetAtt LoadBalancer.LoadBalancerFullName | |
TargetConnectionErrorCountTooHighAlarm: | |
Condition: HasAlertTopic | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Application load balancer could not connect to targets' | |
Namespace: 'AWS/ApplicationELB' | |
MetricName: TargetConnectionErrorCount | |
Statistic: Sum | |
Period: 60 | |
EvaluationPeriods: 1 | |
ComparisonOperator: GreaterThanThreshold | |
Threshold: 0 | |
AlarmActions: | |
- {'Fn::ImportValue': !Sub '${ParentAlertStack}-TopicARN'} | |
Dimensions: | |
- Name: LoadBalancer | |
Value: !GetAtt LoadBalancer.LoadBalancerFullName | |
RecordSet: | |
Condition: HasZone | |
Type: 'AWS::Route53::RecordSet' | |
Properties: | |
AliasTarget: | |
HostedZoneId: !GetAtt 'LoadBalancer.CanonicalHostedZoneID' | |
DNSName: !GetAtt 'LoadBalancer.DNSName' | |
HostedZoneId: {'Fn::ImportValue': !Sub '${ParentZoneStack}-HostedZoneId'} | |
Name: !Sub | |
- '${SubDomainNameWithDot}${HostedZoneName}' | |
- SubDomainNameWithDot: !Ref SubDomainNameWithDot | |
HostedZoneName: {'Fn::ImportValue': !Sub '${ParentZoneStack}-HostedZoneName'} | |
Type: A | |
LoadBalancer: | |
Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' | |
Properties: | |
LoadBalancerAttributes: | |
- Key: 'idle_timeout.timeout_seconds' | |
Value: !Ref LoadBalancerIdleTimeout | |
- Key: 'routing.http2.enabled' | |
Value: 'true' | |
- Key: 'access_logs.s3.enabled' | |
Value: !If [HasS3Bucket, 'true', 'false'] | |
- !If [HasS3Bucket, {Key: 'access_logs.s3.prefix', Value: !Ref 'AWS::StackName'}, !Ref 'AWS::NoValue'] | |
- !If [HasS3Bucket, {Key: 'access_logs.s3.bucket', Value: {'Fn::ImportValue': !Sub '${ParentS3StackAccessLog}-BucketName'}}, !Ref 'AWS::NoValue'] | |
Scheme: !Ref LoadBalancerScheme | |
SecurityGroups: | |
- !Ref ALBSecurityGroup | |
Subnets: !If | |
- HasLoadBalancerSchemeInternal | |
- !Split [',', {'Fn::ImportValue': !Sub '${ParentVPCStack}-SubnetsPrivate'}] | |
- !Split [',', {'Fn::ImportValue': !Sub '${ParentVPCStack}-SubnetsPublic'}] | |
HttpListener: | |
Type: 'AWS::ElasticLoadBalancingV2::Listener' | |
Properties: | |
DefaultActions: | |
- TargetGroupArn: !Ref DefaultTargetGroup | |
Type: forward | |
LoadBalancerArn: !Ref LoadBalancer | |
Port: 80 | |
Protocol: HTTP | |
HttpsListener: | |
Type: 'AWS::ElasticLoadBalancingV2::Listener' | |
Condition: HasLoadBalancerCertificateArn | |
Properties: | |
Certificates: | |
- CertificateArn: !Ref LoadBalancerCertificateArn | |
DefaultActions: | |
- TargetGroupArn: !Ref DefaultTargetGroup | |
Type: forward | |
LoadBalancerArn: !Ref LoadBalancer | |
Port: 443 | |
Protocol: HTTPS | |
ServiceRole: | |
Type: 'AWS::IAM::Role' | |
Properties: | |
ManagedPolicyArns: # TODO get rid of managed policy | |
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole' | |
AssumeRolePolicyDocument: | |
Version: '2008-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: 'ecs.amazonaws.com' | |
Action: 'sts:AssumeRole' | |
Service: | |
Type: 'AWS::ECS::Service' | |
DependsOn: HttpListener | |
Properties: | |
Cluster: {'Fn::ImportValue': !Sub '${ParentClusterStack}-Cluster'} | |
DeploymentConfiguration: | |
MaximumPercent: 200 | |
MinimumHealthyPercent: 50 | |
DesiredCount: !Ref DesiredCount | |
HealthCheckGracePeriodSeconds: !Ref HealthCheckGracePeriod | |
LoadBalancers: | |
- ContainerName: main | |
ContainerPort: 80 | |
TargetGroupArn: !Ref DefaultTargetGroup | |
PlacementStrategies: | |
- Type: spread | |
Field: 'attribute:ecs.availability-zone' | |
- Type: spread | |
Field: instanceId | |
Role: !GetAtt 'ServiceRole.Arn' | |
TaskDefinition: !Ref TaskDefinition | |
CPUUtilizationTooHighAlarm: | |
Condition: HasAlertTopic | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Average CPU utilization over last 10 minutes higher than 80%' | |
Namespace: 'AWS/ECS' | |
Dimensions: | |
- Name: ClusterName | |
Value: {'Fn::ImportValue': !Sub '${ParentClusterStack}-Cluster'} | |
- Name: ServiceName | |
Value: !GetAtt 'Service.Name' | |
MetricName: CPUUtilization | |
ComparisonOperator: GreaterThanThreshold | |
Statistic: Average | |
Period: 300 | |
EvaluationPeriods: 1 | |
Threshold: 80 | |
AlarmActions: | |
- {'Fn::ImportValue': !Sub '${ParentAlertStack}-TopicARN'} | |
ScalableTargetRole: # based on http://docs.aws.amazon.com/AmazonECS/latest/developerguide/autoscale_IAM_role.html | |
Condition: HasAutoScaling | |
Type: 'AWS::IAM::Role' | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: 'application-autoscaling.amazonaws.com' | |
Action: 'sts:AssumeRole' | |
Path: '/' | |
Policies: | |
- PolicyName: ecs | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'ecs:DescribeServices' | |
- 'ecs:UpdateService' | |
Resource: '*' | |
- PolicyName: cloudwatch | |
PolicyDocument: | |
Version: '2012-10-17' | |
Statement: | |
- Effect: Allow | |
Action: | |
- 'cloudwatch:DescribeAlarms' | |
Resource: '*' | |
ScalableTarget: | |
Condition: HasAutoScaling | |
Type: 'AWS::ApplicationAutoScaling::ScalableTarget' | |
Properties: | |
MaxCapacity: !Ref MaxCapacity | |
MinCapacity: !Ref MinCapacity | |
ResourceId: !Sub | |
- 'service/${Cluster}/${Service}' | |
- Cluster: {'Fn::ImportValue': !Sub '${ParentClusterStack}-Cluster'} | |
Service: !GetAtt 'Service.Name' | |
RoleARN: !GetAtt 'ScalableTargetRole.Arn' | |
ScalableDimension: 'ecs:service:DesiredCount' | |
ServiceNamespace: ecs | |
ScaleUpPolicy: | |
Condition: HasAutoScaling | |
Type: 'AWS::ApplicationAutoScaling::ScalingPolicy' | |
Properties: | |
PolicyName: !Sub '${AWS::StackName}-scale-up' | |
PolicyType: StepScaling | |
ScalingTargetId: !Ref ScalableTarget | |
StepScalingPolicyConfiguration: | |
AdjustmentType: PercentChangeInCapacity | |
Cooldown: 300 | |
MinAdjustmentMagnitude: 1 | |
StepAdjustments: | |
- MetricIntervalLowerBound: 0 | |
ScalingAdjustment: 25 | |
ScaleDownPolicy: | |
Condition: HasAutoScaling | |
Type: 'AWS::ApplicationAutoScaling::ScalingPolicy' | |
Properties: | |
PolicyName: !Sub '${AWS::StackName}-scale-down' | |
PolicyType: StepScaling | |
ScalingTargetId: !Ref ScalableTarget | |
StepScalingPolicyConfiguration: | |
AdjustmentType: PercentChangeInCapacity | |
Cooldown: 300 | |
MinAdjustmentMagnitude: 1 | |
StepAdjustments: | |
- MetricIntervalUpperBound: 0 | |
ScalingAdjustment: -25 | |
CPUUtilizationHighAlarm: | |
Condition: HasAutoScaling | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Service is running out of CPU' | |
Namespace: 'AWS/ECS' | |
Dimensions: | |
- Name: ClusterName | |
Value: {'Fn::ImportValue': !Sub '${ParentClusterStack}-Cluster'} | |
- Name: ServiceName | |
Value: !GetAtt 'Service.Name' | |
MetricName: CPUUtilization | |
ComparisonOperator: GreaterThanThreshold | |
Statistic: Average | |
Period: 300 | |
EvaluationPeriods: 1 | |
Threshold: 60 | |
AlarmActions: | |
- !Ref ScaleUpPolicy | |
CPUUtilizationLowAlarm: | |
Condition: HasAutoScaling | |
Type: 'AWS::CloudWatch::Alarm' | |
Properties: | |
AlarmDescription: 'Service is wasting CPU' | |
Namespace: 'AWS/ECS' | |
Dimensions: | |
- Name: ClusterName | |
Value: {'Fn::ImportValue': !Sub '${ParentClusterStack}-Cluster'} | |
- Name: ServiceName | |
Value: !GetAtt 'Service.Name' | |
MetricName: CPUUtilization | |
ComparisonOperator: LessThanThreshold | |
Statistic: Average | |
Period: 300 | |
EvaluationPeriods: 3 | |
Threshold: 30 | |
AlarmActions: | |
- !Ref ScaleDownPolicy | |
Outputs: | |
TemplateID: | |
Description: 'cloudonaut.io template id.' | |
Value: 'ecs/service-dedicated-alb' | |
TemplateVersion: | |
Description: 'cloudonaut.io template version.' | |
Value: '__VERSION__' | |
StackName: | |
Description: 'Stack name.' | |
Value: !Sub '${AWS::StackName}' | |
DNSName: | |
Description: 'The DNS name for the ECS cluster/service load balancer.' | |
Value: !GetAtt 'LoadBalancer.DNSName' | |
Export: | |
Name: !Sub '${AWS::StackName}-DNSName' | |
URL: | |
Description: 'URL to the ECS service.' | |
Value: !Sub 'http://${LoadBalancer.DNSName}' | |
Export: | |
Name: !Sub '${AWS::StackName}-URL' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment