0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RDSの「7日間で勝手に再起動」を完全制覇!?「導入が最も簡単な」自動停止スケジュール設定

Last updated at Posted at 2025-12-26

こんにちは、インサイトテクノロジーの松尾です!

RDSを使っている皆さまの多くが直面する、あの「仕様」、どのように対策されているでしょうか?

「コスト削減のために停止したRDSが、7日経つと勝手に起動してしまう」

検証環境など、週末や夜間は止めておきたいDBがいつの間にか起動して課金されていた…というのはAWSあるあるですよね。私が扱っている環境は本番ではなく開発・検証用環境なので、以下のニーズがありました。

  • 7日間で勝手に自動起動するのを抑止したい
  • 平日の夜に起動しているものは停止したい
  • デフォルトは自動停止対象にしたい
  • でも、特別なタグが設定されている場合は自動停止対象外にしたい

この問題を部分的に解決する記事はネット上に多くありますが、「特定のタグは除外したい」「AuroraとRDSが混在している」「IAMロールの設定が面倒」 といった自分の細かい要件にぴったり合致するものがなかなか見つかりませんでした。そこで、今回、Geminiに助けてもらい つつ、自分がやりたいことをすべて詰め込んだCloudFormationテンプレートを作成しました。おそらく、現時点で 「導入が最も簡単で、かつ柔軟な」 解決策になったと自負しています!

逆に、本記事の手法がばっちりはまらない場合は他の方の記事もぜひ参考にしてみてください。

2. 今回作成した仕組み

今回のCloudFormationで設定される内容は以下です。

  • 毎晩22時にRDSインスタンスを停止 (cron設定はパラメータ化)
  • 日曜日21時30分にRDSインスタンスを起動 (cron設定はパラメータ化) ※その後毎日実行の停止スケジュール(22時)によって再度停止
  • autostop: no のタグがついているものは処理対象外 (タグが付いていない場合は自動停止対象)
  • Auroraにも対応
  • CloudFormationでLambda、IAMロール、スケジュールのすべてを一括構築 (その分、構築時には強い権限が必要)

「ただ止めるだけ」ではなく、運用に配慮した機能を盛り込みました。

3. 爆速導入!CloudFormationテンプレート

以下のCloudFormationテンプレートのコードを template.yaml という名前で保存してください。文字コードはUTF8で保存してください。デプロイ時にIAMロールを作成しますので、その権限があるユーザーでデプロイを実行する必要があります。

AWSTemplateFormatVersion: '2010-09-09'
Description: Full-managed RDS/Aurora Auto Start/Stop Scheduler.

Parameters:
  StopCronExpression:
    Type: String
    Default: "cron(0 22 * * ? *)"
    Description: Cron expression for daily stop (Default is daily 22:00 JST)

  StartCronExpression:
    Type: String
    Default: "cron(30 21 ? * SUN *)"
    Description: Cron expression for Sunday start (Default is Sunday 21:30 JST)

  TimeZone:
    Type: String
    Default: "Asia/Tokyo"
    Description: Timezone for the schedules

Resources:
  # 1. Lambda用IAMロール
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-LambdaRole"
      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: RDSSchedulerMinimumPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              # RDS/Auroraの状態確認とタグ取得
              - Effect: Allow
                Action:
                  - rds:DescribeDBInstances
                  - rds:DescribeDBClusters
                  - rds:ListTagsForResource
                Resource: "*"
              # 特定のアクション(起動・停止)のみ許可
              - Effect: Allow
                Action:
                  - rds:StartDBInstance
                  - rds:StopDBInstance
                  - rds:StartDBCluster
                  - rds:StopDBCluster
                Resource: "*"

  # 2. 制御用Lambda関数 (Runtime: python3.14)
  RDSSchedulerFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${AWS::StackName}-RDSScheduler"
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.14
      Timeout: 120
      Code:
        ZipFile: |
          import boto3
          def lambda_handler(event, context):
              rds = boto3.client('rds')
              action = event.get('action')
              print(f"Executing action: {action}")
              
              instances = rds.describe_db_instances()
              for db in instances['DBInstances']:
                  if 'aurora' in db['Engine']: continue
                  process_resource(rds, db['DBInstanceArn'], db['DBInstanceIdentifier'], db['DBInstanceStatus'], action, False)

              clusters = rds.describe_db_clusters()
              for cluster in clusters['DBClusters']:
                  process_resource(rds, cluster['DBClusterArn'], cluster['DBClusterIdentifier'], cluster['Status'], action, True)

          def process_resource(rds, arn, identifier, status, action, is_cluster):
              tags = {t['Key']: t['Value'] for t in rds.list_tags_for_resource(ResourceName=arn)['TagList']}
              if tags.get('autostop') == 'no':
                  print(f"Skipping {identifier}: Tag 'autostop' is 'no'")
                  return

              if action == 'stop' and status == 'available':
                  if is_cluster: rds.stop_db_cluster(DBClusterIdentifier=identifier)
                  else: rds.stop_db_instance(DBInstanceIdentifier=identifier)
                  print(f"Stopped: {identifier}")
              elif action == 'start' and status == 'stopped':
                  if is_cluster: rds.start_db_cluster(DBClusterIdentifier=identifier)
                  else: rds.start_db_instance(DBInstanceIdentifier=identifier)
                  print(f"Started: {identifier}")

  # 3. Scheduler用IAMロール
  SchedulerExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-SchedulerRole"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: { Service: scheduler.amazonaws.com }
            Action: sts:AssumeRole
      Policies:
        - PolicyName: LambdaInvokePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !GetAtt RDSSchedulerFunction.Arn

  # 4. 停止スケジュール
  DailyStopSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub "${AWS::StackName}-DailyStop"
      ScheduleExpression: !Ref StopCronExpression
      ScheduleExpressionTimezone: !Ref TimeZone
      FlexibleTimeWindow: { Mode: "OFF" }
      Target:
        Arn: !GetAtt RDSSchedulerFunction.Arn
        RoleArn: !GetAtt SchedulerExecutionRole.Arn
        Input: '{"action": "stop"}'

  # 5. 起動スケジュール
  SundayStartSchedule:
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: !Sub "${AWS::StackName}-SundayStart"
      ScheduleExpression: !Ref StartCronExpression
      ScheduleExpressionTimezone: !Ref TimeZone
      FlexibleTimeWindow: { Mode: "OFF" }
      Target:
        Arn: !GetAtt RDSSchedulerFunction.Arn
        RoleArn: !GetAtt SchedulerExecutionRole.Arn
        Input: '{"action": "start"}'

4. 導入とテスト実行の手順

導入ステップ

  1. AWSマネジメントコンソールの CloudFormation で「スタックの作成」をクリック。
  2. 作成した template.yaml をアップロードし、任意のスタック名を入力。
  3. 最終画面で 「IAM リソースが作成される可能性があることを承認します」 にチェックを入れて送信。これだけでインフラが整います。

image.png

テスト実行

設定が正しいか、手動でLambdaを動かしてみましょう。

  1. Lambdaコンソールで作成された関数(スタック名-RDSScheduler)を開く。
  2. 「テスト」タブから新しいイベントを作成。JSONに {"action": "stop"} を指定。({"action": "start"} とすると起動のテストも可能)

image.png

  1. 実行し、ログに Stopped: [DB名] または Skipping... と出れば完璧です!

image.png

image.png

6. まとめ

簡単でしたね!

RDSの自動起動仕様は厄介ですが、今回のように 「定期的に停止命令を出し、週に一度リセット(起動・停止)する」 ロジックを組み込めば、ほぼ完璧に制御可能です。一方で、どうしても止めたくないケースにタグで対象外とすることを可能にしました。また、デフォルトが停止対象なので、タグの付け忘れによる停止漏れも防げます。

同じ悩みを持つ皆さまの助けになれば幸いです!

同様のことを紹介しているサイトの一例

同様のことを紹介しているサイトをいくつかリストしておきますので、必要に応じて参照ください。
一つ一つ設定するならLambdaを使用しなくてもできるのですが、数が多いときや、新たなインスタンス追加などの場合にもデフォルトで対象に加えたい場合には、Lambdaでやってしまうのが一番シンプルかなと思っています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?