Automating EC2 Instance Start/Stop using Serverless Code and CloudWatch Rule
Tags: serveless, ec2, aws, javascript
Published: 2023-05-12 04:47:00

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ocebqhcuhsn6hteslyi9.png)
## Introduction
In this blog post, I am going to remind myself how to use serverless(https://www.serverless.com/) and a CloudWatch rule to automate starting and stopping EC2 instances in AWS. This is particularly useful for teams that need to frequently scale up and down their infrastructure. We'll also cover the cron syntax used to specify the schedule for the CloudWatch rule, including an important limitation to be aware of. By automating this process, we can save time and reduce costs by only running EC2 instances when we need them.

## Prerequisites
Before we get started, make sure we have the following:

- An AWS account with permissions to create EC2 instances, CloudWatch rules, and Lambda functions.
- An EC2 instance that we want to start and stop based on a schedule.
- A basic understanding of serverless and the AWS CloudWatch service.

## Step-by-Step Guide
To create a CloudWatch rule that invokes a Lambda function to start and stop an EC2 instance using serverless code, follow these steps:
- Set up a new serverless project in AWS by running the command "serverless create --template aws-nodejs --path ec2-scheduler".
- Navigate to the "ec2-scheduler" folder and open the "serverless.yml" file.
- Under "provider", add the following lines to allow the Lambda function to start and stop EC2 instances:
```
service: ec2-scheduler

provider:
  name: aws
  runtime: nodejs16.x
  stage: dev
  region: ap-southeast-1
  stackName: ${self:service}
  stackTags:
    Name: ${self:service}
  iam:
    role:
      statements:
        - Effect: "Allow"
          Action:
            - "ec2:StartInstances"
            - "ec2:StopInstances"
          Resource: "*"
```

- Under "custom", add the instance ID of the EC2 instance we want to start and stop, as well as the cron expressions for starting and stopping the instance. For example:
```
custom:
  instanceId: i-0f00
  start: cron(0 0 ? * MON-FRI *)
  stop: cron(15 9 ? * MON-FRI *)
```
This will start the instance at 0:0 AM (UTC) every Monday through Friday and stop the instance at 9:15 PM (UTC) every Monday through Friday.
The cron() expression syntax should conform to the following, where all six fields are required and must be separated by a white space:
`cron(Minutes Hours Day-of-Month Month Day-of-Week Year)`
Each field can have the following values/wildcards:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2fta3mgy60kdk70xsr9.png)
**Notes**: Make Sure We're Not Using * in Both the Day-of-Month and Day-of-Week Fields

- Under "functions", create two functions: "start" and "stop". These functions will call the "startInstances" and "stopInstances" methods of the EC2 service, respectively. For example:
```
functions:
  start:
    handler: handler.start
    events:
      - schedule:
          rate: ${self:custom.start}
          input:
            id: ${self:custom.instanceId}
            region: ${self:provider.region}

  stop:
    handler: handler.stop
    events:
      - schedule:
          rate: ${self:custom.stop}
          input:
            id: ${self:custom.instanceId}
            region: ${self:provider.region}
```

- Create the "handler.js" file and define the "start" and "stop" functions that will be called by the "start" and "stop" functions defined in the "serverless.yml" file. For example:
```
'use strict'

const AWS = require('aws-sdk');

module.exports.start = (event, context, callback) => {
  const ec2 = new AWS.EC2({region: event.region});

  ec2.startInstances({InstanceIds: [event.id]}).promise()
    .then(() => callback(null, `Successfully started ${event.id}`))
    .catch(err => callback(err))
};

module.exports.stop = (event, context, callback) => {
  const ec2 = new AWS.EC2({region: event.region});

  ec2.stopInstances({InstanceIds: [event.id]}).promise()
    .then(() => callback(null, `Successfully stopped ${event.id}`))
    .catch(err => callback(err))
};
```

- Deploy the project by running the command "serverless deploy --aws-profile aws-profile-in-dot-aws-folder". This will create the CloudWatch rule and Lambda function in our AWS account.

- Once the deployment is complete, verify that the CloudWatch rule and Lambda function have been created in the AWS console.

- Test the solution by waiting for the scheduled time to start and stop the EC2 instance. We should see the instance status change in the AWS console.

## FAQ
Q: Can I schedule different start and stop times for different instances?
A: Yes, we can modify the "custom" section of the serverless.yaml file to specify different instance IDs and schedules for each instance.

Q: What if the Lambda function fails to start or stop the instance?
A: The Lambda function will return an error message if it fails to start or stop the instance. We can view the error message in the AWS console or in the CloudWatch logs.

Q: Can I use this solution with other AWS services besides EC2 instances?
A: Yes, we can modify the Lambda function code to interact with other AWS services.

# References
1. https://aws.amazon.com/
2. https://www.serverless.com/framework/docs
3. If our aws account is using mfa - https://repost.aws/knowledge-center/authenticate-mfa-cli 
4. https://www.serverless.com/examples/aws-node-scheduled-cron
5. https://www.designcise.com/web/tutorial/how-to-fix-parameter-scheduleexpression-is-not-valid-serverless-error#check-if-the-cron-expression-syntax-and-values-are-correct

## Conclusion
Automating the start and stop of EC2 instances based on a schedule can help we save time and reduce costs by only running instances when they are needed. Using serverless code and a CloudWatch rule makes it easy to implement this solution in our AWS account. With this solution in place, we can focus on other important tasks while our infrastructure scales up and down automatically.