Using ASG Lifecycle hooks to trigger Lambda functions

1 minute read

AWS ASG Lifecycle hooks


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.

AWS Lambda console


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.

References:

Tags:

Categories:

Updated:

Leave a Comment