Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Stack to create EC2 instances for ECS cluster.
# Stack to create EC2 instances for ECS cluster.
#
# aws cloudformation deploy \
# --stack-name app-cluster-prod \
# --template-file ./aws-cluster-stack.yaml \
# --parameter-overrides \
# KeyName=DEFAULT \
# SecurityGroups=group1,group2 \
# ImageId=ami-123456 \
# InstanceType=c5.large \
# Subnets=subnet-1234,subnet-5678 \
# EcsClusterName=myapp-prod \
# --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
# --no-execute-changeset
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: EC2 instances for ECS cluster.
Parameters:
KeyName:
Description: The EC2 Key Pair to allow SSH access to the instance
Type: AWS::EC2::KeyPair::KeyName
SecurityGroups:
Description: Security group ids to use for the instances.
Type: CommaDelimitedList
ImageId:
Description: ECS-optimised AMI ID for your region. http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
Type: String
InstanceType:
Description: EC2 instance type.
Type: String
Subnets:
Description: Subnet ids for instance placement.
Type: CommaDelimitedList
EcsClusterName:
Description: Name of the ECS cluster.
Type: String
Resources:
## EC2
InstanceRole:
Type: AWS::IAM::Role
Properties:
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
- arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service: ec2.amazonaws.com
Policies:
- PolicyName: logs
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
Resource:
- !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref InstanceRole
AutoscalingLaunchConfig:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SecurityGroups: !Ref SecurityGroups
IamInstanceProfile: !Ref InstanceProfile
UserData:
!Base64:
Fn::Sub: |
- Content-Type: multipart/mixed; boundary="==BOUNDARY=="
MIME-Version: 1.0
--==BOUNDARY==
MIME-Version: 1.0
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
echo ECS_CLUSTER=${ClusterName} >> /etc/ecs/ecs.config
STACK_NAME=${AWS::StackName}
# Install awslogs and the jq JSON parser
yum install -y awslogs jq https://s3-${AWS::Region}.amazonaws.com/amazon-ssm-${AWS::Region}/latest/linux_amd64/amazon-ssm-agent.rpm
# Inject the CloudWatch Logs configuration file contents
cat > /etc/awslogs/awslogs.conf <<- EOF
[general]
state_file = /var/lib/awslogs/agent-state
[/var/log/dmesg]
file = /var/log/dmesg
log_group_name = /var/log/dmesg
log_stream_name = {cluster}/{container_instance_id}
[/var/log/messages]
file = /var/log/messages
log_group_name = /var/log/messages
log_stream_name = {cluster}/{container_instance_id}
datetime_format = %b %d %H:%M:%S
[/var/log/docker]
file = /var/log/docker
log_group_name = /var/log/docker
log_stream_name = {cluster}/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%S.%f
[/var/log/ecs/ecs-init.log]
file = /var/log/ecs/ecs-init.log.*
log_group_name = /var/log/ecs/ecs-init.log
log_stream_name = {cluster}/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%SZ
[/var/log/ecs/ecs-agent.log]
file = /var/log/ecs/ecs-agent.log.*
log_group_name = /var/log/ecs/ecs-agent.log
log_stream_name = {cluster}/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%SZ
[/var/log/ecs/audit.log]
file = /var/log/ecs/audit.log.*
log_group_name = /var/log/ecs/audit.log
log_stream_name = {cluster}/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%SZ
[/var/log/amazon/ssm/amazon-ssm-agent.log]
file = /var/log/amazon/ssm/amazon-ssm-agent.log
log_group_name = amazon-ssm
log_stream_name = agent-$STACK_NAME/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%SZ
[/var/log/amazon/ssm/errors.log]
file = /var/log/amazon/ssm/errors.log
log_group_name = amazon-ssm
log_stream_name = errors-$STACK_NAME/{container_instance_id}
datetime_format = %Y-%m-%dT%H:%M:%SZ
EOF
--==BOUNDARY==
MIME-Version: 1.0
Content-Type: text/x-shellscript; charset="us-ascii"
#!/bin/bash
# Set the region to send CloudWatch Logs data to (the region where the container instance is located)
region=$(curl 169.254.169.254/latest/meta-data/placement/availability-zone | sed s'/.$//')
sed -i -e "s/region = us-east-1/region = $region/g" /etc/awslogs/awscli.conf
--==BOUNDARY==
MIME-Version: 1.0
Content-Type: text/upstart-job; charset="us-ascii"
#upstart-job
description "Configure and start CloudWatch Logs agent on Amazon ECS container instance"
author "Amazon Web Services"
start on started ecs
script
exec 2>>/var/log/ecs/cloudwatch-logs-start.log
set -x
until curl -s http://localhost:51678/v1/metadata
do
sleep 1
done
# Grab the cluster and container instance ARN from instance metadata
cluster=$(curl -s http://localhost:51678/v1/metadata | jq -r '. | .Cluster')
container_instance_id=$(curl -s http://localhost:51678/v1/metadata | jq -r '. | .ContainerInstanceArn' | awk -F/ '{print $2}' )
# Replace the cluster name and container instance ID placeholders with the actual values
sed -i -e "s/{cluster}/$cluster/g" /etc/awslogs/awslogs.conf
sed -i -e "s/{container_instance_id}/$container_instance_id/g" /etc/awslogs/awslogs.conf
service awslogs start
chkconfig awslogs on
end script
--==BOUNDARY==--
- ClusterName: !Ref EcsClusterName
AutoscalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
VPCZoneIdentifier: !Ref SubnetIds
LaunchConfigurationName: !Ref AutoscalingLaunchConfig
MinSize: 0
MaxSize: 0
HealthCheckType: EC2
Tags:
- Key: !Ref AWS::StackName
Value: 'true'
PropagateAtLaunch: true
- Key: Name
Value: !Ref AWS::StackName
PropagateAtLaunch: true
- Key: role
Value: !Ref AWS::StackName
PropagateAtLaunch: true
LifecycleHookRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Principal:
Service: autoscaling.amazonaws.com
Policies:
- PolicyName: SNSAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sns:Publish
Resource: !ImportValue ops-lambdas-prod:EcsLifecycleHookTopicArn
AutoscalingGroupInstanceTerminationHook:
Type: AWS::AutoScaling::LifecycleHook
Properties:
AutoScalingGroupName: !Ref AutoscalingGroup
HeartbeatTimeout: 600
LifecycleTransition: autoscaling:EC2_INSTANCE_TERMINATING
NotificationTargetARN: !ImportValue ops-lambdas-prod:EcsLifecycleHookTopicArn
RoleARN: !GetAtt LifecycleHookRole.Arn
InstanceScaleOutPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: PercentChangeInCapacity
AutoScalingGroupName: !Ref AutoscalingGroup
EstimatedInstanceWarmup: 420
PolicyType: StepScaling
StepAdjustments:
- MetricIntervalLowerBound: 0
MetricIntervalUpperBound: 10
ScalingAdjustment: 10
- MetricIntervalLowerBound: 10
ScalingAdjustment: 30
InstanceScaleInPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AdjustmentType: PercentChangeInCapacity
AutoScalingGroupName: !Ref AutoscalingGroup
EstimatedInstanceWarmup: 420
PolicyType: StepScaling
StepAdjustments:
- MetricIntervalUpperBound: 0
MetricIntervalLowerBound: -10
ScalingAdjustment: -10
- MetricIntervalUpperBound: -10
ScalingAdjustment: -30
InstanceCpuAlarmHigh:
Type: AWS::CloudWatch::Alarm
Properties:
EvaluationPeriods: 5
Statistic: Average
Threshold: 80
AlarmDescription: Alarm if instance CPU high enough to trigger scale out policy.
Period: 60
AlarmActions:
- !Ref InstanceScaleOutPolicy
Namespace: AWS/EC2
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoscalingGroup
ComparisonOperator: GreaterThanOrEqualToThreshold
MetricName: CPUUtilization
InstanceCpuAlarmLow:
Type: AWS::CloudWatch::Alarm
Properties:
EvaluationPeriods: 30
Statistic: Average
Threshold: 30
AlarmDescription: Alarm if instance CPU low long enough to trigger scale in policy.
Period: 60
AlarmActions:
- !Ref InstanceScaleInPolicy
Namespace: AWS/EC2
Dimensions:
- Name: AutoScalingGroupName
Value: !Ref AutoscalingGroup
ComparisonOperator: LessThanOrEqualToThreshold
MetricName: CPUUtilization
Outputs:
AutoscalingGroupName:
Description: Name of ASG.
Value: !Ref AutoscalingGroup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment