Using ASG Lifecycle hooks to trigger Lambda functions
Recently I needed to execute some actions after a EC2 instance was created by an Auto Scaling Group. At first a thought in using the userdata or cloud-init but I needed to create alarms in CloudWatch based on the instance itself, and those alarms must be created dynamically as I used to do with Terraform.
Lifecycle hooks
The Auto Scaling Groups have Lifecycle hooks where you can perform actions when the instance was launched or terminated. For instance you can a create CloudWatch rule to use the event’s message and get the instance-id or the lifecycle hook’s metadata field. An example how this message looks is shown bellow:
{
"EC2InstanceId": "i-0030f3ac294a5764a",
"AutoScalingGroupName": "sync-service",
"LifecycleActionToken": "802cc943-c45a-c8c8-b25d-111222333440",
"LifecycleHookName": "sync-service-StatusCheckFailed-0-launching-hook",
"NotificationMetadata": {
"EvaluationPeriods": 5,
"Missing_data": "breaching",
"AlarmActions": "arn:aws:sns:us-east-1:111111111111:sns-topic",
"AlarmDescription": "The instance has not passed both instance and system status checks",
"Namespace": "AWS/EC2",
"Period": 60,
"ComparisonOperator": "GreaterThanThreshold",
"AlarmName": "sync-status-check",
"Statistic": "Average",
"Threshold": 0,
"MetricName": "StatusCheckFailed"
},
Here the NotificationMetadata contains a JSON with the alarm definition which will be consume for the lambda function to create it.
Triggering the lambda function using a CloudWatch event
You can instruct CloudWatch to respond to Auto Scaling events and trigger a lambda function to pass that message.
Lambda function
Here you have the lambda function that retrieves the instance-id and the lifecycle hook’s metadata:
import boto3
import json
import logging
# Create AWS clients
cw = boto3.client('cloudwatch')
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
# Retrieves instance id from CloudWatch event
def get_instance_id(event):
try:
return event['detail']['EC2InstanceId']
except KeyError as err:
LOGGER.error(err)
return False
def get_metadata(event):
try:
return event['detail']['NotificationMetadata']
except KeyError as err:
LOGGER.error(err)
return False
def lambda_handler(event, context):
session = boto3.session.Session()
instanceid = get_instance_id(event)
metadata = get_metadata(event)
LOGGER.info("instance-id: %s" % instanceid)
LOGGER.info("metadata: %s" % metadata)
# Create Metric
cw.put_metric_alarm(
AlarmName="%s-%s" % (metadata['AlarmName'], instanceid),
AlarmDescription= metadata['AlarmDescription'],
ActionsEnabled=True,
AlarmActions=[
metadata['AlarmActions']
],
MetricName=metadata['MetricName'],
Namespace=metadata['Namespace'],
Statistic=metadata['Statistic'],
Dimensions=[
{
'Name': 'InstanceId',
'Value': instanceid
},
],
Period=metadata['Period'],
EvaluationPeriods=metadata['EvaluationPeriods'],
Threshold=metadata['Threshold'],
TreatMissingData=metadata['Missing_data'],
ComparisonOperator=metadata['ComparisonOperator']
)
IAM Roles and Policies
Remember to create a ROLE and attach the needed policies to your lambda function.
Leave a Comment