Keeping RDS Shutdown Status with AWS SAM

Keeping RDS Shutdown Status with AWS SAM

Takahiro Iwasa
(岩佐 孝浩)
Takahiro Iwasa (岩佐 孝浩)
3 min read
CloudWatch Events Lambda RDS

By default, RDS cannot be shutdown permanently, restarting 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.

Running unnecessary RDS instances would incur costs, so AWS SAM might be useful for keeping RDS shutdown status.

Prerequisites

Install the following on you computer.

Creating SAM Application

Directory Structure

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

AWS SAM Template

The example below uses cron expression as Lambda trigger (line 24), although RDS events with EventBridge should be a more suitable choice.

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:
        # CloudWatch Events Rule
        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

Leave it empty. The AWS Lambda runtime environment has boto3 installed by default, so there is no need to include it in your requirements.txt.

app.py

The script stops the RDS instance only when its status 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 the actual value.

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

Build and deploy with the following command.

sam build
sam deploy

Testing

Stop your RDS instance, leave it for 7 days, and check the Lambda logs. It should contain the following records.

DB instance - database-1 - has been stopped.

Confirm the RDS instance status is stopped with the following command.

$ aws rds describe-db-instances --db-instance-identifier <YOUR_RDS_INSTANCE_NAME> | grep DBInstanceStatus
            "DBInstanceStatus": "stopped",
Takahiro Iwasa
(岩佐 孝浩)

Takahiro Iwasa (岩佐 孝浩)

Software Developer at iret, Inc.
Architecting and developing cloud native applications mainly with AWS. Japan AWS Top Engineers 2020-2023