以下の内容はhttps://engineers.fenrir-inc.com/entry/2025/02/19/122326より取得しました。


タグで停止対象を判別するAuroraの自動停止・起動をCloudFormationで作成してみた

こんにちは、インフラエンジニアの川島です。

Auroraの自動停止・自動起動をCloudFormationで作成したので紹介したいと思います。

AWSでコスト削減をしようとすると目に付くのはやはりAuroraだと思います。

Auroraは起動している間、料金が発生し続け、EC2と比べてもとても高いです。

開発環境では常時起動は必要ないため一時停止しようとしても、手動では最大でも7日間しか停止することは出来ず、停止したと思ってたのにいつの間にか起動して料金がすごい事になっていた、なんてこともよくあることと思います。

Amazon Aurora DB クラスターの停止と開始 - Amazon Aurora

本ブログでは下記にて紹介されていた手法をCloudFormationを用いて実装しました。

7 日間を超える期間にわたって Amazon Aurora クラスターを停止する | AWS re:Post

全体像

今回構築するアーキテクチャとしては下記になります。

EventBrdigeが定期起動し、LambdaがAuroraの停止・起動のAPIを叩く事で自動停止・起動を実装しています。

本手法はAuroraに付けられているタグによって停止対象を判別している事が特徴的です。

そのため、停止対象のAuroraが増減した場合にもAuroraのタグを変更するだけでよく、他の手法と比べて手軽に設定することが可能となっています。

設定するパラメータと作成するリソースとしては下記になります。

設定するパラメータ 内容
ProjectName 任意の値
EnvCode 任意の値 (dev, stg, …)
EventBridgeState 自動停止・起動の有効、無効 (ENABLED, DISABLED)
StartSchedule 自動起動時刻 (cron式でUTC時刻で設定)
StopSchedule 自動停止時刻 (cron式でUTC時刻で設定)
作成するリソース リソース名
Lambda用Role LambdaRole
起動Lambda AuroraAutoStartLambda
起動Lambda用リソースベースポリシー PermissionForEventsToInvokeStartLambda
起動EventBridge AuroraStartEventRule
停止Lambda AuroraAutoStopLambda
停止Lambda用リソースベースポリシー PermissionForEventsToInvokeStopLambda
停止EventBridge AuroraStopEventRule

テンプレート

AWSTemplateFormatVersion: 2010-09-09
Description: "Create AuroraAutoStop template"
Parameters:
  ProjectName:
    Description: "Project name"
    Type: "String"
  EnvCode:
    Description: "Environment type"
    Type: "String"
  EventBridgeState:
    Description : "EventBridge state"
    Type: "String"
    AllowedValues:
      - "ENABLED"
      - "DISABLED"
  StartSchedule:
    Description: "Start Aurora schedule (cron UTC)"
    Type: "String"
  StopSchedule:
    Description: "Stop Aurora schedule (cron UTC)"
    Type: "String"

Resources:
  AuroraAutoStartLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          rds = boto3.client('rds')

          def lambda_handler(event, context):

            #Start DB clusters
            dbs = rds.describe_db_clusters()
            for db in dbs['DBClusters']:
              #Check if DB cluster stopped. Start it if eligible.
              if (db['Status'] == 'stopped'):
                doNotStart=1
                try:
                  GetTags=rds.list_tags_for_resource(ResourceName=db['DBClusterArn'])['TagList']
                  for tags in GetTags:
                  #if tag "autostart=yes" is set for cluster, start it
                    if(tags['Key'] == 'autostart' and tags['Value'] == 'yes'):
                      result = rds.start_db_cluster(DBClusterIdentifier=db['DBClusterIdentifier'])
                      print ("Starting cluster: {0}.".format(db['DBClusterIdentifier']))
                  if(doNotStart == 1):
                    doNotStart=1
                except Exception as e:
                  print ("Cannot start cluster {0}.".format(db['DBClusterIdentifier']))
                  print(e)
                          
          if __name__ == "__main__":
              lambda_handler(None, None)
      FunctionName: !Sub "${ProjectName}-${EnvCode}-aurora-auto-start-function"
      Handler: index.lambda_handler
      Runtime: python3.12
      Role: !GetAtt LambdaRole.Arn
      Timeout: 10

  AuroraAutoStopLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          rds = boto3.client('rds')

          def lambda_handler(event, context):

            #Stop DB clusters
            dbs = rds.describe_db_clusters()
            for db in dbs['DBClusters']:
              #Check if DB cluster started. Stop it if eligible.
              if (db['Status'] == 'available'):
                doNotStop=1
                try:
                  GetTags=rds.list_tags_for_resource(ResourceName=db['DBClusterArn'])['TagList']
                  for tags in GetTags:
                  #if tag "autostop=yes" is set for cluster, stop it
                    if(tags['Key'] == 'autostop' and tags['Value'] == 'yes'):
                      result = rds.stop_db_cluster(DBClusterIdentifier=db['DBClusterIdentifier'])
                      print ("Stopping cluster: {0}.".format(db['DBClusterIdentifier']))
                  if(doNotStop == 1):
                    doNotStop=1
                except Exception as e:
                  print ("Cannot stop cluster {0}.".format(db['DBClusterIdentifier']))
                  print(e)
                          
          if __name__ == "__main__":
            lambda_handler(None, None)
      FunctionName: !Sub "${ProjectName}-${EnvCode}-aurora-auto-stop-function"
      Handler: index.lambda_handler
      Runtime: python3.12
      Role: !GetAtt LambdaRole.Arn
      Timeout: 10

  LambdaRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: AuroraStartStopPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - rds:StartDBCluster
                  - rds:StopDBCluster
                  - rds:ListTagsForResource
                  - rds:DescribeDBInstances
                  - rds:StopDBInstance
                  - rds:DescribeDBClusters
                  - rds:StartDBInstance
                Resource: "*"

  PermissionForEventsToInvokeStartLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref AuroraAutoStartLambda
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt AuroraStartEventRule.Arn

  AuroraStartEventRule:
    Type: "AWS::Events::Rule"
    Properties:
      Name: !Sub "${ProjectName}-${EnvCode}-aurora-start-rule"
      Description: "Push AuroraStartLambda Batch event rule"
      ScheduleExpression: !Ref StartSchedule
      State: !Ref EventBridgeState
      Targets: 
        - Arn: !GetAtt AuroraAutoStartLambda.Arn
          Id: StartLambdaFunction

  PermissionForEventsToInvokeStopLambda:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref AuroraAutoStopLambda
      Action: lambda:InvokeFunction
      Principal: events.amazonaws.com
      SourceArn: !GetAtt AuroraStopEventRule.Arn

  AuroraStopEventRule:
    Type: "AWS::Events::Rule"
    Properties:
      Name: !Sub "${ProjectName}-${EnvCode}-aurora-stop-rule"
      Description: "Push AuroraStopLambda Batch event rule"
      ScheduleExpression: !Ref StopSchedule
      State: !Ref EventBridgeState
      Targets: 
        - Arn: !GetAtt AuroraAutoStopLambda.Arn
          Id: StopLambdaFunction

実装方法

対象Auroraにタグを設定

停止対象のAuroraのDBクラスターにタグ autostart:yes, autostop:yes を設定します。

CloudFormationを用いてリソースを作成

上記テンプレートをYAMLファイルとして保存し、コンソール画面からアップロードし、パラメータに適切な値を入力します。

ProjectNameEnvCodeは任意の値を入力します。

EventBridgeStateは自動停止を有効化する場合はENABLED、無効化する場合はDISABLEDを選択します。

StartScheduleStopScheduleにはcron式かつUTCで値を入力します。 (例 : cron(15 13 ? * MON *))

cron 式と rate 式を使用して Amazon EventBridge でルールをスケジュールする - Amazon EventBridge

まとめ

今回はタグで停止対象を判別するAuroraの自動停止・起動を紹介いたしました。

停止対象を直接指定していないため、新しいDBを作成した時にもタグを追加するだけで自動停止・起動の対象とする事ができます。

この記事がどなたかのお役に立てば幸いです。




以上の内容はhttps://engineers.fenrir-inc.com/entry/2025/02/19/122326より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14