Efficiently Managing RDS Shutdown with AWS SAM

Efficiently Managing RDS Shutdown with AWS SAM

Takahiro Iwasa
Takahiro Iwasa
3 min read
CloudWatch Events Lambda RDS

Introduction

By default, Amazon RDS instances cannot remain stopped indefinitely, as they are automatically restarted after seven days.

You can stop a DB instance for up to seven days. If you don’t manually start your DB instance after seven days, your DB instance is automatically started so that it doesn’t fall behind any required maintenance updates.

This behavior can lead to unnecessary costs for unused instances. In this blog post, we explore how AWS SAM can help automate the shutdown of RDS instances and ensure they remain stopped.

Prerequisites

Ensure the following are installed on your system:

Creating an AWS SAM Application

Directory Structure

Below is the directory structure for the SAM application:

/
|-- rds_shutdown/
|   |-- app.py
|   `-- requirements.txt
|-- samconfig.toml
`-- template.yaml

AWS SAM Template

The template defines a Lambda function triggered by a CloudWatch Events rule using a cron expression (line 23). Alternatively, consider using RDS events with EventBridge for more granular control.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Using a CloudWatch Events rule, RdsShutdown keeps an RDS instance shutdown after 1 week.

Parameters:
  RdsDbInstance:
    Type: String

Resources:
  RdsShutdownFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: rds_shutdown/
      Environment:
        Variables:
          DB_INSTANCE: !Ref RdsDbInstance
      Events:
        ScheduleEvent:
          Type: Schedule
          Properties:
            Description: An event rule for RdsShutdownFunction
            Enabled: True
            Schedule: 'cron(* */1 * * ? *)'  # Every hour
      FunctionName: rds_shutdown
      Handler: app.lambda_handler
      MemorySize: 128
      Role: !GetAtt IamRole.Arn
      Runtime: python3.8
      Timeout: 10

  IamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: policy1
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - rds:DescribeDBInstances
                  - rds:StopDBInstance
                Resource: !Sub arn:aws:rds:*:${AWS::AccountId}:db:${RdsDbInstance}
      RoleName: rds-shutdown

  LogsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${RdsShutdownFunction}
      RetentionInDays: 30

Python Script

requirements.txt

No additional dependencies are needed because boto3 is already included in the AWS Lambda runtime.

app.py

This script checks the status of the RDS instance and stops it only when it is available.

import logging
import os

import boto3


# Environment Variables
DB_INSTANCE = os.environ.get('DB_INSTANCE')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

client = boto3.client('rds')


def lambda_handler(event, context):
    if not DB_INSTANCE:
        # Exit when a DB Instance is not specified.
        logger.error('DB_INSTANCE environment variable is not specified.')
        return

    # Get the DB Instance status.
    status = get_db_instance_status(DB_INSTANCE)

    if status == 'available':
        # Stop when the status is available.
        client.stop_db_instance(DBInstanceIdentifier=DB_INSTANCE)
        logger.info(f'DB instance - {DB_INSTANCE} - has been stopped.')


def get_db_instance_status(db_instance: str) -> str:
    # See https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Status.html
    response = client.describe_db_instances(DBInstanceIdentifier=db_instance)
    return response['DBInstances'][0]['DBInstanceStatus']

samconfig.toml

Replace <YOUR_S3_BUCKET> and <YOUR_RDS_INSTANCE_NAME> with your values.

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "rds-shutdown"
s3_bucket = "<YOUR_S3_BUCKET>"
s3_prefix = "rds-shutdown"
region = "ap-northeast-1"
capabilities = "CAPABILITY_IAM CAPABILITY_NAMED_IAM"
parameter_overrides = "RdsDbInstance=\"<YOUR_RDS_INSTANCE_NAME>\""

Build and Deploy

Use the following commands to build and deploy the application:

sam build
sam deploy

Testing

Stop your RDS instance, leave it for seven days, and check the Lambda logs for the following message:

DB instance - database-1 - has been stopped.

Verify the status of the instance using this AWS CLI command:

aws rds describe-db-instances --db-instance-identifier <YOUR_RDS_INSTANCE_NAME> | grep DBInstanceStatus

You should see the following output:

"DBInstanceStatus": "stopped",

Conclusion

Using AWS SAM and Lambda functions to automate the shutdown of RDS instances is an efficient way to minimize costs and manage resources. However, remember that RDS instances cannot remain stopped indefinitely.

Happy Coding! 🚀

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.