RDSを定期的に起動/停止する機能をCloudFormationで作ってみる

2021.05.09

こんにちは、データアナリティクス事業本部@那覇オフィスの下地です。

RDSインスタンスは停止することができますが、停止した7日後には自動で起動します(一時的に Amazon RDS DB インスタンスを停止する)。そこで、6日間隔でRDSを自動起動/停止する機能を作成してみようと思います。

1. 全体像

作成する全体像は以下図のようになります。

必要なリソースを下記表にまとめます。こちらをCloudFormation(cfn)を使用して作成していきます。

作成するリソース リソース名
起動用のロール LambdaRoleStartRDS
起動lambda StartRDSLambda
起動EventBridge StartRDSScheduleEvent
停止用のロール LambdaRoleStopRDS
停止lambda StopRDSLambda
停止EventBridge StopRDSScheduleEvent

2. 作ってみる

RDSを自動起動/停止する機能を作成します。cfnで作成するリソースは以下の3種類です。

  • AWS::IAM::Role
  • AWS::Lambda::Function
  • AWS::Events::Rule

2.1 cronの設定

AWS::Events::Ruleではcronを使用します。設定時には以下の6つの項目を設定する必要があります(Schedule Expressions for Rules)。

# 設定項目(分 時 特定の日 月 曜日 年)
cron(Minutes Hours Day-of-month Month Day-of-week Year)

今回、起動イベントの30分後に停止イベントを実施すること、6日間隔で起動するため以下の様に設定します。

# 起動
cron(0 10 */6 * ? *)
# 停止
cron(30 10 */6 * ? *)

2.2 最小権限の設定

LambdaからRDSの起動(もしくは停止)する際のpolicyには最小権限の設定のためRDSのARNを指定します。 こちらは、AWS::IAM::RoleのPolicies項目にて!Joinを使用し該当するRDSのARNを作成します。作成する内容は以下のようになります。

# policyに記載する内容
Resource: !Join ["", [ "arn:aws:rds:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":db:", !Ref RdsInstanceName] ]

# 作成されるRDSのARN
"Resource": "arn:aws:rds:リージョン名:アカウントID:db:RDSインスタンス名"

2.3 cfnのコード全体

では最終的に作成したファイルを記載します。

template.yml

AWSTemplateFormatVersion: "2010-09-09"
Description: Ability to automatically start and stop RDS

Parameters: 
  RdsInstanceName:
    Description: "rds instance name"
    Type: String
    Default: RDSインスタンス名

Resources: 
  LambdaRoleStartRDS:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: PolicyStartRDS
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "rds:StartDBInstance"
            Resource: !Join ["", [ "arn:aws:rds:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":db:", !Ref RdsInstanceName] ]
          - Effect: Allow
            Action:
            - "logs:CreateLogGroup"
            - "logs:CreateLogStream"
            - "logs:PutLogEvents"
            Resource: "*"

  StartRDSLambda: 
    Type: "AWS::Lambda::Function"
    Properties: 
      Handler: "index.handler"
      Role: 
        Fn::GetAtt: 
          - "LambdaRoleStartRDS"
          - "Arn"
      Environment:
        Variables:
          rds_instance: !Ref RdsInstanceName
      Runtime: "python3.7"
      Code: 
        ZipFile: |
          import boto3
          import os
          instance = os.environ["rds_instance"]
    
          def handler(event, context):
              rds = boto3.client('rds')
              rds.start_db_instance(DBInstanceIdentifier=instance)

  StartRDSScheduleEvent:
    Type: AWS::Events::Rule
    Properties:
      Description: ’StartRDS schedule event for lambda’
      ScheduleExpression: 'cron(0 10 */6 * ? *)'
      State: ENABLED
      Targets:
        - Arn: !GetAtt StartRDSLambda.Arn
          Id: ScheduleEvent1Target
    DependsOn:
      - StartRDSLambda


  LambdaRoleStopRDS:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: PolicyStopRDS
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - "rds:StopDBInstance"
            Resource: !Join ["", [ "arn:aws:rds:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":db:", !Ref RdsInstanceName] ]
          - Effect: Allow
            Action:
            - "logs:CreateLogGroup"
            - "logs:CreateLogStream"
            - "logs:PutLogEvents"
            Resource: "*"

  StopRDSLambda: 
    Type: "AWS::Lambda::Function"
    Properties: 
      Handler: "index.handler"
      Role: 
        Fn::GetAtt: 
          - "LambdaRoleStopRDS"
          - "Arn"
      Environment:
        Variables:
          rds_instance: !Ref RdsInstanceName
      Runtime: "python3.7"
      Code: 
        ZipFile: |
          import boto3
          import os
          instance = os.environ["rds_instance"]
    
          def handler(event, context):
              rds = boto3.client('rds')
              rds.stop_db_instance(DBInstanceIdentifier=instance)

  StopRDSScheduleEvent:
    Type: AWS::Events::Rule
    Properties:
      Description: ’StopRDS schedule event for lambda’
      ScheduleExpression: 'cron(30 10 */6 * ? *)'
      State: ENABLED
      Targets:
        - Arn: !GetAtt StopRDSLambda.Arn
          Id: ScheduleEvent1Target
    DependsOn:
      - StopRDSLambda

2.4 実行確認

コンソール画面からtemplate.ymlファイルをアップロードし、該当のRDSインスタンス名を記載して実行します。実行後、以下図のようにリソースが作成されていることが確認できました。

3. まとめ

6日ごとにRDSを起動/停止する仕組みを作成してみました。cfnに慣れておらず作成する際にいろいろ調べましたが、作れてしまうと管理がとても容易になると実感しました。 この記事がどなたかの助けになれば幸いです。

参考リンク