Automatically Stopping Instances During Off Hours AWS Lambda with Tag

For those with staging, development, or QA environments provisioned in EC2 using the on-demand billing model, you may be paying more than you need. If you have employees working in these environments during business hours, it doesn’t make sense to have them running 24/7, if it’s not necessary. Now, there may be cases where you would like a staging environment to run at all times, but for those where you don’t, you might as well stop the instances to save money.

For example, suppose your QA team does testing on an application running on several on-demand EC2 instances from 8:00AM to 5:00PM, Monday thru Friday. If no one stops those instances, your paying for those resources while your team isn’t even at work! By stopping the instances during off-hours, you can slash your bill for those instances by more than half, especially if you include the weekend.

EC2 Off Hours Management

The following is an automated solution for stopping EC2 instances at a particular hour (7:00PM, Monday thru Friday) and starting them back up at a particular hour (7:00AM, Monday thru Friday). It operates using 2 separate Lambda functions (one for stopping, one for starting) triggered by scheduled CloudWatch events. Within each function, a search is done for EC2 instances that have a specific tag so you can designate exactly which resources you want this process to affect. Instances without this tag are ignored. In this example, I’m choosing to include any EC2 instance that has a tag of “Environment” where the value is set to “Development”, but the code and values shown can easily be modified to suit your own requirements.

Environment Tag

Let’s dig in.

Creating an IAM policy for the Lambda function stopping instances at off-hours:

  1. Navigate to IAM in your management console.
  2. Select “Policies” in the sidebar.
  3. Click “Create Policy”.
  4. Select “Create Your Own Policy”.
  5. Enter an appropriate policy name and description.
  6. Paste the following JSON into the policy document:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "ec2:DescribeInstances",
                    "ec2:StopInstances"
                ],
                "Resource": [
                    "*"
                ]
            }
        ]
    }
  7. Click “Create Policy”.

Creating the IAM role for the Lambda function stopping instances at off-hours:

  1. Select “Roles” in the sidebar.
  2. Click “Create New Role”.
  3. Enter an appropriate role name and click “Next Step”.
  4. Select “AWS Lambda” within the AWS Service Roles.
  5. Change the filter to “Customer Managed”, check the box of the policy you just created, and click “Next Step”.
  6. Click “Create Role”.

Creating the Lambda function stopping instances at off-hours:

  1. Navigate to Lambda in your management console.
  2. Click “Create a Lambda function”.
  3. Select the “Blank Function” blueprint.
  4. Under “Configure triggers”, click the grey box and select “CloudWatch Events – Schedule”.
  5. Enter an appropriate rule name and description.
  6. I want the instances to be stopped at 7:00PM after the workday. Since I live in Arizona (UTC-7:00), the correct cron expression for this is 0 2 ? * MON-FRI *. You’ll need to change this based on when you want the instances to stop and what timezone you’re in.
  7. Check the box to “Enable trigger” and click “Next”.
  8. Enter an appropriate function name and description. Select Node.js for the runtime.
  9. Under “Lambda function code”, select “Edit code inline” for the Code entry type and paste the following code in the box
  10. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    var AWS = require("aws-sdk");
    exports.handler = (event, context, callback) => {
        var ec2 = new AWS.EC2();
        var describeParams = { Filters: [
            {
                Name:"tag:Environment",
                Values: [
                    "Development"
                ]
            },
            {
                Name:"instance-state-name",
                Values: [
                    "running"
                ]
            }
        ]};
        var instances = [];
        ec2.describeInstances(describeParams, function(err, data) {
            if (err) {
                console.log(err, err.stack);
            } else {
                console.log(data);
                for (var i = 0; i < data.Reservations.length; i++) {
                    for (var j = 0; j < data.Reservations[i].Instances.length; j++) {
                        var instanceId = data.Reservations[i].Instances[j].InstanceId;
                        if (instanceId != undefined && instanceId != null && instanceId != "") {
                            instances.push(instanceId);  
                        }
                    }
                }
                if (instances.length > 0){
                    var stopParams = { InstanceIds: instances };
                    ec2.stopInstances(stopParams, function(err,data) {
                        if (err) {
                           console.log(err, err.stack);
                        } else {
                           console.log(data);
                        }
                    });  
                }
           }
        });
    };
  11. Leave Handler as “index.handler”.
  12. Choose to use an existing role and select the IAM role you created earlier for stopping instances.
  13. You may want to select a higher value for the Timeout depending on how many instances will be involved in this process.
  14. Leave the other default values and click “Next”.
  15. Click “Create function”.

Creating the IAM policy for the Lambda function starting instances at on-hours:

  1. Navigate to IAM in your management console.
  2. Select “Policies” in the sidebar.
  3. Click “Create Policy”.
  4. Select “Create Your Own Policy”.
  5. Enter an appropriate policy name and description.
  6. Paste the following JSON into the policy document:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "ec2:DescribeInstances",
                    "ec2:StartInstances"
                ],
                "Resource": [
                    "*"
                ]
            }
        ]
    }
  7. Click “Create Policy”.

Creating the IAM role for the Lambda function starting instances at on-hours:

  1. Select “Roles” in the sidebar.
  2. Click “Create New Role”.
  3. Enter an appropriate role name and click “Next Step”.
  4. Select “AWS Lambda” within the AWS Service Roles.
  5. Change the filter to “Customer Managed”, check the box of the policy you just created, and click “Next Step”.
  6. Click “Create Role”.

Creating the Lambda function starting instances at on-hours:

  1. Navigate to Lambda in your management console.
  2. Click “Create a Lambda function”.
  3. Select the “Blank Function” blueprint.
  4. Under “Configure triggers”, click the grey box and select “CloudWatch Events – Schedule”.
  5. Enter an appropriate rule name and description.
  6. I want the instances to be started at 7:00AM before the workday. Since I live in Arizona (UTC-7:00), the correct cron expression for this is 0 14 ? * MON-FRI *. You’ll need to change this based on when you want the instances to start and what timezone you’re in.
  7. Check the box to “Enable trigger” and click “Next”.
  8. Enter an appropriate function name and description. Select Node.js for the runtime.
  9. Under “Lambda function code”, select “Edit code inline” for the Code entry type and paste the following code in the box:
  10. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    var AWS = require("aws-sdk");
    exports.handler = (event, context, callback) => {
        var ec2 = new AWS.EC2();
        var describeParams = { Filters: [
            {
                Name:"tag:Environment",
                Values: [
                    "Development"
                ]
            },
            {
                Name:"instance-state-name",
                Values: [
                    "stopped"
                ]
            }
        ]};
        var instances = [];
        ec2.describeInstances(describeParams, function(err, data) {
            if (err) {
                console.log(err, err.stack);
            } else {
                console.log(data);
                for (var i = 0; i < data.Reservations.length; i++) {
                    for (var j = 0; j < data.Reservations[i].Instances.length; j++) {
                        var instanceId = data.Reservations[i].Instances[j].InstanceId;
                        if (instanceId != undefined && instanceId != null && instanceId != "") {
                            instances.push(instanceId);  
                        }
                    }
                }
                if (instances.length > 0){
                    var stopParams = { InstanceIds: instances };
                    ec2.startInstances(stopParams, function(err,data) {
                        if (err) {
                           console.log(err, err.stack);
                        } else {
                           console.log(data);
                        }
                    });  
                }
           }
        });
    };
  11. Leave Handler as “index.handler”.
  12. Choose to use an existing role and select the IAM role you created earlier for starting instances.
  13. You may want to select a higher value for the Timeout depending on how many instances will be involved in this process.
  14. Leave the other default values and click “Next”.
  15. Click “Create function”.

Feel free to modify this solution to achieve your specific needs. Hopefully, this will allow you to save some money on your AWS bill every month.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s