How to Schedule EC2 Instances to Stop/Start Automatically
Tags: aws, ec2, cost, lambda
Published: 2024-04-25 18:54:26

Many users require their EC2 instances to operate only at specific times during the day or week, or exclusively for certain daily batch processes. Keeping instances running continuously can be costly. While numerous solutions exist—most involving a combination of Lambda functions and additional AWS resources—I find them overly complex for such a straightforward task. To simplify, I've developed a method using just one Lambda function and CloudWatch as an event bridge to schedule operations like a cron job. In the following sections, I'll detail all the steps needed to implement this efficient approach for your EC2 instance.

## Step 1: Create an IAM Role for Lambda Execution

**1. Initiate Role Creation**: Begin by navigating to the IAM (Identity and Access Management) section in your AWS Management Console. Here, you’ll create a new role that your Lambda function will assume when executing.

**2. Select Service**: In the role creation process, choose 'Lambda' from the list of AWS services to define the use case. This setting specifies that the role is intended to be used with AWS Lambda.

![ec2-schedule-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyu290chzz05a3j5u877.png)



**3. Attach Policies**: Search for and select the 'AmazonEC2FullAccess' policy. Attaching this policy grants the role full access rights to manage EC2 instances, encompassing operations such as start, stop, and monitor. Confirm and save this selection.

![ec2-schedule-2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs8a98xum8s2q5jyq2h5.png)


![ec2-schedule-3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3n6y2squ7wg9glaleyl.png)

**4. Role Review and Creation**: After selecting the policy, review the settings to ensure everything is configured correctly. Provide a name for the role that easily identifies its use case, such as "LambdaEC2ControlRole". Complete the process by creating the role.

![ec2-schedule-4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgdg0nfh1eq7ps1j807z.png)

This newly created IAM role will act as the Lambda function’s execution role, authorizing it to perform specified actions on EC2 instances as per the permissions granted under the 'AmazonEC2FullAccess' policy.

## Step 2: Create a Lambda function

Now it's time to write the Lambda function. Follow these steps to create a new Lambda function using the specified code:

**1. Access the AWS Lambda Console**: Navigate to the AWS Lambda section within your AWS Management Console. Click on "Create function" to start the process of setting up a new Lambda function.

**2. Configure the Function**:
   - **Name your Function**: Give your Lambda function a descriptive name that indicates its purpose, such as "EC2InstanceScheduler".
   - **Runtime Selection**: Choose the appropriate runtime that supports your programming language. For Python code, select a Python runtime (e.g., Python 3.9).

   - **Change default execution role** : select 'Use an existing role' then select role we created earlier 'LambdaEC2ControlRole'


![ec2-schedule-5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrzc3888j1ebthq492mc.png)


**3. Input the Code**:
   - **Edit the Code Inline**: In the function code editor, paste the following Python script:

```python
import boto3
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

ec2 = boto3.client('ec2')

def lambda_handler(event, context):
    instances = ec2.describe_instances()
    for reservation in instances['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            print ("Check "+instance_id)
            instance_tz = "UTC"  
            schedule = None
            
            for tag in instance.get('Tags', []):
                if tag['Key'] == 'schedule':
                    schedule = tag['Value']
                elif tag['Key'] == 'schedule-tz':
                    instance_tz = tag['Value']
            
            if schedule:
                schedule_parts = schedule.split('-')
                tz = ZoneInfo(instance_tz)  
                now = datetime.now(tz)  
                
                start_time_str, end_time_str = schedule_parts
                start_hour, start_minute = map(int, start_time_str.split(':'))
                end_hour, end_minute = map(int, end_time_str.split(':'))

                start_time = now.replace(hour=start_hour, minute=start_minute, second=0, microsecond=0)
                end_time = now.replace(hour=end_hour, minute=end_minute, second=0, microsecond=0)

                if end_time < start_time:
                    end_time += timedelta(days=1)
                
                if start_time <= now <= end_time:
                    if instance['State']['Name'] == 'stopped':
                        ec2.start_instances(InstanceIds=[instance_id])
                        print(f"Starting instance {instance_id}")
                else:
                    if instance['State']['Name'] == 'running':
                        ec2.stop_instances(InstanceIds=[instance_id])
                        print(f"Stopping instance {instance_id}")

```

This script imports necessary libraries—boto3 for interacting with AWS services, `datetime` and `timedelta` for managing date and time operations, and `ZoneInfo` for handling timezone-related information.


**4. Publish the Function**:
   - **Publish a New Version**: With the code saved, publish the new function by clicking on the 'Publish' option, usually found next to the save button. Publishing is crucial as it makes the function version immutable and enables you to reference this specific setup in triggers or other AWS services.

![ec2-schedule-6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/erf577zv32slma5pnzb8.png)

**5. Test Your Function**:
   - **Create a Test Event**: Click on the ‘Test’ button to set up a test event. You can use a default template or create a custom test event if your function requires specific input parameters.
   - **Execute and Review**: Execute the test by clicking ‘Test’ again. Observe the function's output in the execution log below the code editor. Your function should execute without errors, and ideally, it would output something indicative of its operation, such as the IDs of managed EC2 instances.


![ec2-schedule-7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhywuklak9dwjwwm14az.png)


![ec2-schedule-8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/du69nfdm6bqw7e8dlaoy.png)

By completing these steps, your Lambda function with the provided Python script will be set up and ready. This function can now be integrated into workflows or triggered by events within your AWS environment, such as using CloudWatch Events for scheduled execution.

## Step 3: Configure CloudWatch EventBridge to Trigger Lambda Function

**1. Select Event Source**:
   - **Navigate to Triggers**: In your Lambda function’s configuration page, locate and select the ‘Add trigger’ option.


![ec2-schedule-9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lep5u896oo1umaxmy4g.png)

   - **Choose EventBridge (CloudWatch Events)**: From the list of available triggers, select ‘EventBridge (CloudWatch Events)’ as the source. This integration allows you to schedule when the Lambda function should be triggered automatically.

**2. Define the Schedule**:
   - **Schedule Expression**: You will need to specify how frequently the Lambda function should be triggered. If you want the function to run every 10 minutes, enter the schedule expression as `rate(10 minutes)`. This sets the function to execute at a regular interval, every 10 minutes.
   - **Using Cron for Specific Timing**: If your needs are more specific, you can use a cron expression. For example, to have the function run every 5 minutes from Monday to Friday, you would enter the expression as `cron(0/5 * ? * MON-FRI *)`. This expression ensures that the function triggers at every 5-minute interval during the weekdays. More info https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html


![ec2-schedule-10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kaw23bijhgjsdhoj1vro.png)

**3. Save the Trigger Configuration**:
   - **Confirm Settings**: After entering your desired schedule expression, review the settings to make sure everything is correct.
   - **Save**: Click the ‘Save’ button to apply and activate the trigger. This step is crucial as it commits your settings, enabling EventBridge to start triggering your Lambda function according to the defined schedule.


![ec2-schedule-11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a7pd4ayoxdb37f21gg8.png)

By completing these steps, you integrate CloudWatch EventBridge as a trigger for your Lambda function, allowing it to execute automatically based on the schedule you've set. This setup is ideal for automating tasks on a timed basis without manual intervention.

## Step 4: Add Labels to Your EC2 Instances for Scheduled Stop/Start

We're almost through with the setup complexities. The next step is to label the EC2 instances that you want to include in the stop/start schedule. This Lambda function supports multiple instances, allowing you to apply a scheduled stop/start routine to as many EC2 instances as needed using this single setup, without the necessity for additional functions or configurations.

**How to Label Your EC2 Instances:**

**1. Identify the Instances**: Determine which EC2 instances you want to have an automated stop/start schedule.

**2. Navigate to EC2 Management Console**: Go to your AWS EC2 dashboard.

**3. Select the Instance**: Click on the EC2 instance you want to schedule.

**4. Manage Tags**: In the instance overview pane, find and click on the ‘Tags’ tab. Here, you can manage and assign new tags.


**5. Add Schedule Tags**:
   - **Add a ‘schedule’ Tag**: Create a tag with the key as `schedule` and the value specifying the active time range, such as `10:58-11:01`. This value means that the instance will automatically turn on daily between 10:58 AM and 11:01 AM.
   - **Add a ‘schedule-tz’ Tag**: Create another tag with the key as `schedule-tz` and the value as the timezone in which the schedule operates, such as `America/New_York`.  Timezone list https://en.wikipedia.org/wiki/List_of_tz_database_time_zones


![ec2-schedule-12](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4f9rpi4rzni5fpqf4tet.png)


![ec2-schedule-13](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttsois5b4a1o3m2cn08o.png)

**Selective Functionality**:
This labeling system ensures that the Lambda function will only affect instances tagged accordingly. If an EC2 instance does not have these specific tags, the Lambda function will not alter its state. This feature is particularly useful if you have instances that must remain operational at all times. By only labeling the instances you want to manage, you can maintain continuous operation for others without interference.

By following these steps, you effectively configure selective automated scheduling for your EC2 instances, enhancing management efficiency and cost-effectiveness. This method simplifies scaling as adding any new EC2 instance to the schedule is as easy as tagging it appropriately.

That concludes our guide. I hope you find this article useful and that it helps you reduce your AWS expenses. Happy scheduling with your EC2 instances!