AWS DevOps & Developer Productivity Blog

Auto Scaling AWS OpsWorks Instances

This post will show you how to integrate Auto Scaling groups with AWS OpsWorks so you can leverage  the native scaling capabilities of Amazon EC2 and the OpsWorks Chef configuration management solution.

Auto Scaling ensures you have the correct number of EC2 instances available to handle your application load.  You create collections of EC2 instances (called Auto Scaling groups), specify desired instance ranges for them, and create scaling policies that define when instances are provisioned or removed from the group.

AWS OpsWorks helps configure and manage your applications.  You create groups of EC2 instances (called stacks and layers) and associate to them configuration such as volumes to mount or Chef recipes to execute in response to lifecycle events (for example, startup/shutdown).  The service streamlines the instance provisioning and management process, making it easy to launch uniform fleets using Chef and EC2.

The following steps will show how you can use an Auto Scaling group to manage EC2 instances in an OpsWorks stack.

Integrating Auto Scaling with OpsWorks

This example will require you to create the following resources:

Auto Scaling group: This group is responsible for EC2 instance provisioning and release.

Launch configuration: A configuration template used by the Auto Scaling group to launch instances.

OpsWorks stack: Instances provisioned by the Auto Scaling group will be registered with this stack.

IAM instance profile: This profile grants permission to your instances to register with OpsWorks.

Lambda function: This function handles deregistration of instances from your OpsWorks stack.

SNS topic: This topic triggers your deregistration Lambda function after Auto Scaling terminates an instance.

Step 1: Create an IAM instance profile

When an EC2 instance starts, it must make an API call to register itself with OpsWorks.  By assigning an IAM instance profile to the instance, you can grant it permission to make OpsWorks calls.

Open the IAM console, choose Roles, and then choose Create New Role. Type a name for the role, and then choose Next Step.  Choose the Amazon EC2 Role, and then select the check box next to the AWSOpsWorksInstanceRegistration policy.  Finally, choose Next Step, and then choose Create Role. As the name suggests, the AWSOpsWorksInstanceRegistration policy only allows the API calls required to register an instance. Because you will have to make two more calls for this demo,  add the following inline policy to the new role.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "opsworks:AssignInstance",
                "opsworks:DescribeInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Step 2: Create an OpsWorks stack

Open the AWS OpsWorks console.  Choose the Add Stack button from the dashboard, and then  choose Sample Stack. Make sure the Linux OS option is selected, and  then choose Create Stack.  After the stack has been created, choose Explore the sample stack. Choose the layer named Node.js App Server.  You will need the IDs of this sample stack and layer in a later step.  You can extract both from the URL of the layer page, which uses this format:

https://console.aws.amazon.com/opsworks/home?region=us-west-2#/stack/ YOUR-OPSWORKS-STACK-ID/layers/ YOUR-OPSWORKS-LAYER-ID.

Step 3: Create a Lambda function

This function is responsible for deregistering an instance from your OpsWorks stack.  It will be invoked whenever an EC2 instance in the Auto Scaling group is terminated.

Open the AWS Lambda console and choose the option to create a Lambda function.  If you are prompted to choose a blueprint, choose Skip.  You can give the function any name you like, but be sure to choose the Python 2.7 option from the Runtime drop-down list.

Next, paste the following code into the Lambda Function Code text entry box:

import json
import boto3

def lambda_handler(event, context):
    message = json.loads(event['Records'][0]['Sns']['Message'])
    
    if (message['Event'] == 'autoscaling:EC2_INSTANCE_TERMINATE'):
        ec2_instance_id = message['EC2InstanceId']
        ec2 = boto3.client('ec2')
        for tag in ec2.describe_instances(InstanceIds=[ec2_instance_id])['Reservations'][0]['Instances'][0]['Tags']:
            if (tag['Key'] == 'opsworks_stack_id'):
                opsworks_stack_id = tag['Value']
                opsworks = boto3.client('opsworks', 'us-east-1')
                for instance in opsworks.describe_instances(StackId=opsworks_stack_id)['Instances']:
                    if ('Ec2InstanceId' in instance):
                        if (instance['Ec2InstanceId'] == ec2_instance_id):
                            print("Deregistering OpsWorks instance " + instance['InstanceId'])
                            opsworks.deregister_instance(InstanceId=instance['InstanceId'])
    return message

Then, from the Role drop-down list, choose Basic Execution Role.  On the page that appears, expand  View Policy Document, and then choose Edit

Next, paste the following JSON into the policy text box:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "opsworks:DescribeInstances",
        "opsworks:DeregisterInstance"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
       "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

Choose Allow.  On the Lambda creation page, change the Timeout field to 0 minutes and 15 seconds, and choose Next.  Finally, choose Create Function.

Step 4: Create an SNS topic

The SNS topic you create in this step will be responsible for triggering an execution of the Lambda function you created in step 3.  It is the glue that ties Auto Scaling instance terminations to corresponding OpsWorks instance deregistrations.

Open the Amazon SNS console.  Choose Topics, and then choose Create New Topic.  Type topic and display names, and then choose Create Topic.  Select the check box next to the topic you just created, and from Actions, choose Subscribe to Topic.  From the Protocol drop-down list, choose AWS Lambda.  From the Endpoint drop-down list, choose the Lambda function you created in step 3.  Finally, choose Create Subscription.

Step 5: Create a launch configuration

This configuration contains two important settings: security group and user data.  Because you’re deploying a Node.js app that’s will listen on port 80, you must use a security group that has this port open. Then there’s the user data script that’s executed when an instance starts. Here we make the call to register the instance with OpsWorks.

 

 

Open the Amazon EC2 console and create a launch configuration. Use the latest release of Amazon Linux, which should be the first operating system in the list. On the details page, under IAM role, choose the instance profile you created in step 2. Expand the Advanced Details area and paste the following code in the User data field. Because this is a template, you will have to replace YOUR-OPSWORKS-STACK-ID and YOUR-OPSWORKS-LAYER-ID with the OpsWorks stack and layer ID you copied in step 1.

#!/bin/bash
sed -i'' -e 's/.*requiretty.*//' /etc/sudoers
pip install --upgrade awscli
INSTANCE_ID=$(/usr/bin/aws opsworks register --use-instance-profile --infrastructure-class ec2 --region us-east-1 --stack-id YOUR-OPSWORKS-STACK-ID --override-hostname $(tr -cd 'a-z' < /dev/urandom |head -c8) --local 2>&1 |grep -o 'Instance ID: .*' |cut -d' ' -f3)
/usr/bin/aws opsworks wait instance-registered --region us-east-1 --instance-id $INSTANCE_ID
/usr/bin/aws opsworks assign-instance --region us-east-1 --instance-id $INSTANCE_ID --layer-ids YOUR-OPSWORKS-LAYER-ID

Step 6. Create an Auto Scaling group

On the last page of the Launch Configuration wizard, choose Create an Auto Scaling group using this launch configuration. In the notification settings, add a notification to your SNS topic for the terminate event. In the tag settings, add a tag with key opsworks_stack_id. Use the OpsWorks stack ID you entered in the User data field as the value. Make sure the Tag New Instances check box is selected.

Conclusion

Because the default desired size for your Auto Scaling group is 1, a single instance will be started in EC2 immediately.  You can confirm this through the EC2 console in a few seconds:

A few minutes later, the instance will appear in the OpsWorks console:

To confirm your Auto Scaling group instances will be deregistered from OpsWorks on termination, change the Desired value from 1 to 0.  The instance will disappear from the EC2 console. Within minutes, it will disappear from the OpsWorks console, too.

Congratulations! You’ve configured an Auto Scaling group to seamlessly integrate with AWS OpsWorks. Please let us know if this helps you scale instances in OpsWorks or if you have tips of your own.