Manages start and stop schedules to save costs on EC2 instances.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
.travis.yml
LICENSE
Makefile
README.md
lambda_function.py
lambda_function_test.py
requirements.txt
setup.cfg
setup.py

README.md

EC2 Instance Stop Start

A simple AWS Lambda function that starts and stops EC2 instances to help save on costs. You can schedule starts and stops via CloudWatch Events, set up an API Gateway endpoint to call at will... or invoke the function using any other supported method (or directly via the Lambda API) providing it's possible to configure a custom payload.

The function itself is written in Python 3 and was based initially on this AWS example.

Setup

  1. Create a new Python 3.6 Lambda function (eg. in the AWS Lambda console). It will need access to startInstances and/or stopInstances for the EC2 instance(s) you wish to modify, so you will also need to create a suitable IAM role if you don't have one already.
  2. Either fork this repository and modify .travis.yml to deploy to your new function (function name, role name, AWS access details, etc.) OR open lambda_function.py and copy the code directly into your new Lambda function.

Invoking on a Schedule

  1. Visit the CloudWatch console and add a new rule.

  2. Select the Schedule option, and create the schedule for when you would like to start or stop your instance(s) - you'll probably want to use the Cron expression option (note that unlike many cron implementations, AWS requires a question mark (?) for either the day-of-month or day-of-week fields - see here for full documentation).

  3. Click Add target, and select your Lambda function (and version or alias, if applicable).

  4. Expand Configure input and select the Constant (JSON text) option. Enter the following text, using either 'start' or 'stop' for the action, and of course specifying the region and the instance IDs you wish to operate on:

    { "action": "start", "region": "ap-southeast-2", "instances": [ "i-0155bb8e6d7dddf46", "i-0155bb8e6d7dddf46" ] }
    
  5. Finally, click Configure details, fill out the requested information, and click Create rule! Your instance(s) should now be started or stopped along with the schedule you created.

Repeat these instructions to create additional schedules for other instances, regions, or start/stop actions.

Invoking via API Gateway

  1. Visit the API Gateway console and either create a new API, or select an existing one you want to use.

  2. Think about how you want to structure your resources. For example, if you only have one EC2 instance to manage, you might set up a structure such as /server/start and /server/stop.

  3. Under Resources, click Actions -> Create Resource, and enter a name and path. Following the above example, you'll enter 'server' at this point. Click Create Resource. If you need another path level, create a new resource to add that - for example, 'start'.

  4. With your new resource selected, click Actions -> Create Method. Select the method you wish to use (although it's not technically correct, use GET if you want to easily call it via your browser's address bar), and click the tick.

  5. Find your Lambda function (leave 'Use Lambda Proxy integration' deselected), and click Save.

  6. Now under your method settings, click Integration Request, then scrolling down to and expand Body Mapping Templates.

  7. Click Add mapping template, enter 'application/json', and click the check mark.

  8. Scroll down, and for Generate template, select Empty. Replace the empty curly braces with the following, using either 'start' or 'stop' for the action, and of course changing the region and instance IDs you wish to operate on, and the IP addresses authorised to access the endpoint (leave the authorised-ips key out entirely if you don't want to restrict access):

    {
       "action": "start",
       "region": "ap-southeast-2",
       "instances": [
         "i-0155bb8e6d7dddf46",
         "i-0155bb8e6d7dddf46"
       ],
       "source-ip": "$context.identity.sourceIp"
     }
    
  9. Save it, click Stages over on the left, and Create a stage (such as 'v1') if you don't already have one.

  10. Deploy your API to your stage, then visit your endpoint! It should look something like https://12abc3defg.execute-api.ap-southeast-2.amazonaws.com/v1/server/start, depending on your API's invoke URL and the values you entered for your stage and resource(s). If everything is hooked up correctly, your server should start (or stop)!

Repeat these steps to create additional endpoints to service different actions, regions or instances. You could also set your API up to take and map dynamic input directly from the request (via the path, query string or request headers).

Securing your endpoint

Unless you want anyone who finds your API to be able to start and stop your servers, you'll want to think about securing it - such as with API keys or IAM policies.

There's also a built-in way to this function to restrict access by IP address. You shouldn't rely on this as your only defence, but it's worthwhile doing if you can.

The mapping template we set up earlier already configures API Gateway to send through the source IP address, so now you just need to add your authorised IP(s). You can do this by adding the AUTHORISED_IPS environment variable to your Lambda function, setting it to a comma delimited list of IPs such as eg. 123.45.67.89,98.76.54.32.

Alternatively, add an authorised-ips key directly to the mapping template we added above:

"authorised-ips": [
  "123.45.67.89",
  "98.76.54.32"
]

Which of these methods you choose depends on whether you want to configure the IPs once for each invocation of your function... or once for every endpoint that you set up.

Development

Clone (or fork and clone) the repository to your machine.

If you have Docker running, you can test the function locally by running make test, although you may want to replace the instance ID in the Makefile with an instance you control 🙂. You'll also need to have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY exported as environment variables to pass into the Docker container.

Note that unit tests are not yet written.

License

MIT.