How to schedule RDS instances (start/stop) easily
Tags: aws, rds, database, serverless
Published: 2024-04-24 15:54:53

A potential game changer in AWS is to schedule when your RDS should be running since every cent that we can save is important. AWS provides some solutions also, but they create new dependencies since they use AWS Dynamo DB, which I wouldn't say is the best idea. That's why I created based on some [tutorials](https://aws.amazon.com/blogs/database/schedule-amazon-rds-stop-and-start-using-aws-lambda/) a new one based on one Lambda only. Let's check it:

## Step 1. Create an IAM policy and role for Lambda

Go to the **IAM console**, under **Access Management** in the navigation pane, and choose **Policies**. Then, click **Create policy**.

![Preview 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3j369w0f325m9zjuv1s7.jpg)

Go to the **JSON** tab, and enter the following policy code:

```json
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
              "rds:DescribeDBClusterParameters",
              "rds:StartDBCluster",
              "rds:StopDBCluster",
              "rds:DescribeDBEngineVersions",
              "rds:DescribeGlobalClusters",
              "rds:DescribePendingMaintenanceActions",
              "rds:DescribeDBLogFiles",
              "rds:StopDBInstance",
              "rds:StartDBInstance",
              "rds:DescribeReservedDBInstancesOfferings",
              "rds:DescribeReservedDBInstances",
              "rds:ListTagsForResource",
              "rds:DescribeValidDBInstanceModifications",
              "rds:DescribeDBInstances",
              "rds:DescribeSourceRegions",
              "rds:DescribeDBClusterEndpoints",
              "rds:DescribeDBClusters",
              "rds:DescribeDBClusterParameterGroups",
              "rds:DescribeOptionGroups"
          ],
          "Resource": "*"
      }
  ]
}
```

Next, click **Review Policy**. In the Create Policy section choose a name like **rdsstopstart**.

![preview 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duosgsvbhyhcpa71ba06.jpg)

After this, choose the option **Create policy**.

Now that you have the policy ready, you need to create the **IAM role**. In the navigation pane, choose **Roles** followed by clicking **Create role**.

![Preview 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/puyewv089pv7yzrvbus6.jpg)

For Select type of trusted entity, choose **AWS service**. Next, For Common use cases, choose **Lambda**. Then, choose **Next: Permissions**.

![Preview 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oieingsrq1dw87uzuab.jpg)

In the new screen search for and select the policy you created before **rdsstopstart**.

![Preview 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9zasr6vgzwbdnghzr6i.jpg)

Go to the last screen and for the **Role name**, enter **rdsLambda**. Then click **Create role**.

![Preview 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xb8obaf70z8cbj0ivkus.jpg)

##Step 2. Create your Lambda function to stop the database

For this section, you need to create one Lambda function that will stop and start the databases. On the **Lambda** console, choose **Functions** in the navigation pane and select **Create function**.

![Preview 7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jog4ixoux35cjgqv3dma.jpg)

- For **Function name**, enter _rds-scheduler_.
- For **Runtime**, choose _Python 3.12+_.
- For **Execution role**, select _Use an existing role_.
- For **Existing role**, choose the role you created (**rdsLambda**).

![Preview 8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1n22m0o5z0zu4cxskvtx.jpg)

After this, choose to **Create function**. On the function details page, go to the function code and replace the sample code with the following one:

```python
import boto3
import os

def lambda_handler(event, context):
    region = os.environ['REGION']
    key = os.environ['KEY']
    value = os.environ['VALUE']
    
    action = event.get('action')
    
    if action not in ['start', 'stop']:
        print('Invalid action specified. Please provide either "start" or "stop".')
        return
    
    client = boto3.client('rds', region_name=region)
    
    def handle_rds_instances():
        response = client.describe_db_instances()
        v_read_replica = []
        
        for i in response['DBInstances']:
            read_replica = i['ReadReplicaDBInstanceIdentifiers']
            v_read_replica.extend(read_replica)
            
        for i in response['DBInstances']:
            if i['Engine'] not in ['aurora-mysql', 'aurora-postgresql']:
                if i['DBInstanceIdentifier'] not in v_read_replica and len(i['ReadReplicaDBInstanceIdentifiers']) == 0:
                    arn = i['DBInstanceArn']
                    resp2 = client.list_tags_for_resource(ResourceName=arn)
                    
                    if 0 == len(resp2['TagList']):
                        print('DB Instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                    else:
                        for tag in resp2['TagList']:
                            if tag['Key'] == key and tag['Value'] == value:
                                if action == 'start':
                                    start_instance(i)
                                elif action == 'stop':
                                    stop_instance(i)
                            elif tag['Key'] != key and tag['Value'] != value:
                                print('DB instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                            elif len(tag['Key']) == 0 or len(tag['Value']) == 0:
                                print('DB Instance {0} is not part of autoShutdown'.format(i['DBInstanceIdentifier']))
                elif i['DBInstanceIdentifier'] in v_read_replica:
                    print('DB Instance {0} is a Read Replica.'.format(i['DBInstanceIdentifier']))
                else:
                    print('DB Instance {0} has a read replica. Cannot shutdown & start a database with Read Replica'.format(i['DBInstanceIdentifier']))
            else:
                for tag in resp2['TagList']:
                    if tag['Key'] == key and tag['Value'] == value and i['MultiAZ']:
                        if action == 'start':
                            start_aurora_cluster(i['DBInstanceArn'])
                        else:
                            stop_aurora_cluster(i['DBInstanceArn'])
    
    def start_instance(instance):
        if instance['DBInstanceStatus'] == 'available':
            print('{0} DB instance is already available'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'stopped':
            client.start_db_instance(DBInstanceIdentifier=instance['DBInstanceIdentifier'])
            print('Started DB Instance {0}'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'starting':
            print('DB Instance {0} is already in starting state'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'stopping':
            print('DB Instance {0} is in stopping state. Please wait before starting'.format(instance['DBInstanceIdentifier']))

    def stop_instance(instance):
        if instance['DBInstanceStatus'] == 'available':
            client.stop_db_instance(DBInstanceIdentifier=instance['DBInstanceIdentifier'])
            print('Stopped DB instance {0}'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'stopped':
            print('DB Instance {0} is already stopped'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'starting':
            print('DB Instance {0} is in starting state. Please stop the cluster after starting is complete'.format(instance['DBInstanceIdentifier']))
        elif instance['DBInstanceStatus'] == 'stopping':
            print('DB Instance {0} is already in stopping state.'.format(instance['DBInstanceIdentifier']))

    def stop_aurora_cluster(cluster_arn):
        """Stops an Aurora cluster identified by ARN if tagged for shutdown."""
        client.stop_db_cluster(DBClusterIdentifier=cluster_arn)
        print(f"Stopped Aurora Cluster with ARN: {cluster_arn}")

    def start_aurora_cluster(cluster_arn):
        """Stops an Aurora cluster identified by ARN if tagged for shutdown."""
        client.start_db_cluster(DBClusterIdentifier=cluster_arn)
        print(f"Started Aurora Cluster with ARN: {cluster_arn}")
    
    handle_rds_instances()
```

Then click **Save**.

The function needs a couple of parameters that you can configure in the **Configuration** tab. You need to navigate to the **Environment variables** section. The variables are:

- **KEY**: it's a tag name that you would choose like SNOOZE or DEV-TEST.
- **REGION**: the DB location like us-east-1 or eu-west-1.
- **VALUE**: the tag value like Auto-Shutdown or true.

![Preview 9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k4sio0qifg8gxbjdws99.jpg)

Then, go to the **Test** tab

And replace the Event JSON with one of the following options:

- `{ "action": "start" }` _=> We will share this option in EventBridge to start the tagged DBs._
- `{ "action": "stop" }` _=> We will share this option in EventBridge to stop the tagged DBs._

Navigate to your **RDSs** and tag your DB:

![Preview 10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9uo9msybg6jlt7c91t06.png)

Then click on **Test**. If all is successful you will see a screen like the following one:

![Preview 11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhxkn8m5scg6eums72l3.jpg)

## Step 3. Create your Amazon EventBridge Schedulers

**Amazon EventBridge schedulers** will trigger the Lambda function to start or stop the databases based on the schedules you defined. You will need to create two, one to start and one to stop the DBs.

![Preview 10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9tofc8q3ct6kt232hrvn.jpg)

In the **Schedule pattern**, you define the time choosing a **Recurring schedule**:

![Preview 11](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jlhu2zky7mu2ay8j1ikd.jpg)

Then, you click **Next**. In the **Target Detail**, you choose **AWS Lambda**:

![Preview 12](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3delag67893h8321f83.jpg)

In the **Invoke** section, you choose your **Lambda function** like **rds-scheduler**:

![Preview 13](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y9xn4t300ay6ytn47clz.jpg)

And also you configure your **Payload** with:

- `{ "action": "start" }` _=> We will share this option in EventBridge to start the tagged DBs._
- `{ "action": "stop" }` _=> We will share this option in EventBridge to stop the tagged DBs._

After that click and choose **Skip to review and create schedule**. On the new page, click **Create schedule**.

After this, you can repeat the process for the stop scheduler. This is all that you need to schedule your RDSs.

**Note:**
Thanks to Yesh Tanamala, Sharath Lingareddy, and Varun Mahajan for starting this process that I improved.

### Follow me on:

|  Personal  |  LinkedIn  |YouTube|Instagram|Cyber Prophets|Sharing Your Stories|
|:----------|:----------|:------------:|:------------:|:------------:|:------------:|
|[![Personal](https://raw.githubusercontent.com/FANMixco/federiconavarrete/master/img/favicons/favicon.png)](https://federiconavarrete.com)|[![LinkedIn](https://i.stack.imgur.com/idQWu.png)](https://www.linkedin.com/in/federiconavarrete)|[![YouTube](https://i.stack.imgur.com/CFPMR.png)](https://youtube.com/c/FedericoNavarrete)|[![Instagram](https://i.stack.imgur.com/PIfqY.png)](https://www.instagram.com/federico_the_consultant)|[![RedCircle Podcast](https://i.stack.imgur.com/4XICF.png)](https://redcircle.com/shows/cyber-prophets)|[![RedCircle Podcast](https://i.stack.imgur.com/4XICF.png)](https://redcircle.com/shows/sharing-your-stories)|


[![sponsor me](https://raw.githubusercontent.com/FANMixco/Xamarin-SearchBar/master/bmc-rezr5vpd.gif)](https://www.buymeacoffee.com/fanmixco)