How to start and stop AWS EC2 instance based on a time based schedule

Solution 1:

Update

AWS have released a tool called the "Instance Scheduler", including a full configuration guide which is linked from that page. It looks to be an enhancement of the EC2 Scheduler I describe below, with a few more features, but it's essentially the same thing.

The guide below will still work, but it's probably better to look at the instance scheduler for new installations.

Original Post

AWS have a tool called EC2 Scheduler that gives you very flexible control over starting and stopping EC2 instances.

The tool allows you to define default start and stop times when you set the tool up, which you can change later. You can choose which instances get controlled, you and you can specify different start and stop times for each instance using tags.

While it's a great tool the documentation is somewhat vague and confusing. It's like the documentation has been written by an engineer who wrote the tool and knows everything about it, rather than a technical writer.

Note : if you have feedback or corrections comments are appreciated. If you have a question based on this please start your own question.

What is EC2 Scheduler

This tool is a Lambda Function that works with Cloudwatch Events and DynamoDB. It's deployed using a Cloudformation template, which also sets up the necessary IAM roles and policies. You can read about the architecture here.

AWS EC2 Scheduler Architecture

Deployment

Start by going to this page and clicking "launch solution". Right now the direct link is here, but it could change.

Select the region you want the resources deployed to at the top of the console. The script controls EC2 instances in any region, but it runs in one region.

Tagging EC2 Instances

This is covered in the documentation here, but it's not as simple as it could be.

You control which instances are started and stopped by tagging your instances.

The simplest case requires you to tag each EC2 instance you want to be started and stopped according to the schedule. To do this find your EC2 instance in the console, click tags, and create this tag

EC2 Instance Tagging for Scheduler

To enable copy and paste:

  • Key: scheduler:ec2-startstop
  • Value: true

If you want to have specific instance started and stopped on a different schedule you append additional information to the tag key and value. For example if you want an instance to start at 1500 UTC and stop at 2400 UTC on Tuesday, Thursday and Friday you enter the following.

Key: scheduler:ec2-startstop:late Value: 1500;2400;utc;tue,thu,fri

Note that the word "late" can be any string, "late" has no special meaning.

You can convert UTC to your local time using this tool.

You can use the tag editor to bulk tag instances. That could more easily let you set up bulk tagging, which could be useful for having different settings for dev, test, and production. I doubt you'd use this on production though.

CloudFormation Parameters

When you run the CloudFormation template you have to enter a lot of parameters. Most you can leave at default. Here's some of the most important parameters

  • Stack Name: call it anything you like. It's just what it's called in CloudFormation.
  • Custom Tag Name: this is the "key" of the tag you put against EC2 instance. Leave it at the default value unless you have a good reason, or need multiple installations.
  • Default start/stop time: Default UTC time to start and stop the instances
  • DynamoDB: settings are stored in DynamoDB. You can change the table name and such. Because the DynamoDB free tier doesn't expire most people are unlikely to be charged.
  • (second screen) Permissions - this is a red herring, see the section below. Leave it as default, and be running as an administrator when you try to set up EC2 Scheduler.
  • Notification options: I found it useful to set up SNS notifications so I could validate it was working. I haven't spent the time to work out how to disable them, I just deleted it re-ran the Cloudformation template to reinstall.

Permissions, Policies, and Roles

The Permissions / IAM role section of the CloudFormation template is a red herring - ie it's largely irrelevant. It specifies only the role used to run the CloudFormation script, it makes no difference to the resources created or the role used when the lambda function runs. In retrospect this is obvious, but it wasn't obvious to me when I started.

Whatever role you run this script as the same role and inline permissions are created within IAM. The Lambda function runs using an "ec2 scheduler role" that the script creates.

I've included my policies below in case they're helpful for anyone.

CloudWatch Events and Metrics

If you want to see logs from your Lambda Function go into Cloudwatch Events. The logging is quite good. There are metrics as well, so you can see when it runs, time it runs for, etc.

Additional

The code for the lambda function is available on Github.

Policies

These aren't generally necessary, but could be for someone so I'll include them.

Policy for IAM Role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:DescribeTags",
                "iam:CreateRole",
                "iam:GetRole",
                "iam:PassRole",
                "iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:DeleteRole",
                "dynamodb:*",
                "lambda:*",
                "SNS:Publish",
                "events:*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "S3:GetObject",
            "Resource": [
                "arn:aws:s3:::solutions-us-west-2",
                "arn:aws:s3:::solutions-us-west-2/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:StopInstances",
                "ec2:StartInstances"
            ],
            "Resource": [
                "arn:aws:ec2:us-west-2:123456789012:instance/i-0d112345Ab6789012"
            ]
        }
    ]
}

Trust policy for IAM role

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "cloudformation.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Solution 2:

If you just want to start and stop instances, here's another take on this which also make use of the Lambda service. It assumes you want to control a specific instance id. You can control multiple instances by adding more ids separated by a comma. (ex: 'i-3453453','i-45656745'). You can find the id of your instance in the AWS console Instances section.

In the Lambda console

  1. Open the AWS Lambda console, and choose Create function.
  2. Choose Author from scratch.
  3. Enter a Name for your function, such as "StopEC2Instances."
  4. For Runtime, select Python 2.7
  5. Expand the Role drop-down menu, and choose Create a custom role. This opens a new tab or window in your browser.
  6. In the IAM Role drop-down menu, select Create a new IAM Role, and enter a Role Name, such as “lambda_start_stop_ec2."
  7. Choose View Policy Document, Edit, and then choose Ok when prompted to read the documentation. Replace all the text in the policy with this:

Code below

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Start*",
        "ec2:Stop*"
      ],
      "Resource": "*"
    }
  ]
}
  1. Choose Allow to finish creating the role and return to the AWS Lambda console.
  2. To stop your instances, replace all text in the Function code editor with the following:

Code below

import boto3
region = ' eu-west-1'
instances = ['i-0dd344443184503fa']

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name=region)
    ec2.stop_instances(InstanceIds=instances)
    print 'stopped your instances: ' + str(instances)

Remember to replace region and instance values with your own.

  1. From the Runtime drop-down menu, choose Python2.7.
  2. In Basic settings, enter 10 seconds for the function Timeout.
  3. Choose Save.
  4. Repeat all the steps to create another function that will start your instances, but then use this python script for starting it all:

Code below

import boto3
region = 'eu-west-1'
instances = [' i-0dd344443184503fa']

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name=region)
    ec2.start_instances(InstanceIds=instances)
    print 'started your instances: ' + str(instances)

Schedule the functions

Here you will create a CloudWatch Event that will trigger your Lambda function at night

  1. Open the Amazon CloudWatch console.
  2. Choose Events, and then choose Create rule.
  3. Choose Schedule under Event Source.
  4. Enter an interval of time or cron expression that tells Lambda when to stop your instances. For more information on the correct syntax, see Schedule Expression Syntax for Rules.

Note: Cron expressions are evaluated in UTC. Be sure to adjust the expression for your preferred time zone. Here is an example that will run the function every day at 08:00 GMT/UTC):

0 08 * * ? *
  1. Choose Add target, and then choose Lambda function.
  2. For Function, choose the Lambda function that stops your instances.
  3. Choose Configure details.
  4. Enter the following information in the provided fields: For Name, enter a meaningful name, such as "StopEC2Instances." For Description, add a meaningful description, such as “stops EC2 instances every day at night.” For State, select Enabled.
  5. Choose Create rule.

To restart your instances in the morning, repeat these steps and use your preferred start time. If you want to send a mail message whenever the functions fail, you can set up an SNS topic and configure the sending of that message under Debugging in the Lmbda Function Creation Window.

The source of all this can be found here: AWS documentation