Skip to content

Instantly share code, notes, and snippets.

@TheDeveloper
Last active February 28, 2024 12:00
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save TheDeveloper/242ebf1bfb11b8b6f9a9b0f454897ac7 to your computer and use it in GitHub Desktop.
Save TheDeveloper/242ebf1bfb11b8b6f9a9b0f454897ac7 to your computer and use it in GitHub Desktop.
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
@robsonalves
Copy link

Thanks for it, that helped me a lot, this part of the code:

ops-lambdas-prod:EcsLifecycleHookTopicArn

Is it another stack that created this SNS to use here? Do you have some examples of that?

Thanks again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment