こんにちは、インサイトテクノロジーの松尾です!
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. 導入とテスト実行の手順
導入ステップ
- AWSマネジメントコンソールの CloudFormation で「スタックの作成」をクリック。
- 作成した
template.yamlをアップロードし、任意のスタック名を入力。 - 最終画面で 「IAM リソースが作成される可能性があることを承認します」 にチェックを入れて送信。これだけでインフラが整います。
テスト実行
設定が正しいか、手動でLambdaを動かしてみましょう。
- Lambdaコンソールで作成された関数(
スタック名-RDSScheduler)を開く。 - 「テスト」タブから新しいイベントを作成。JSONに
{"action": "stop"}を指定。({"action": "start"}とすると起動のテストも可能)
- 実行し、ログに
Stopped: [DB名]またはSkipping...と出れば完璧です!
6. まとめ
簡単でしたね!
RDSの自動起動仕様は厄介ですが、今回のように 「定期的に停止命令を出し、週に一度リセット(起動・停止)する」 ロジックを組み込めば、ほぼ完璧に制御可能です。一方で、どうしても止めたくないケースにタグで対象外とすることを可能にしました。また、デフォルトが停止対象なので、タグの付け忘れによる停止漏れも防げます。
同じ悩みを持つ皆さまの助けになれば幸いです!
同様のことを紹介しているサイトの一例
同様のことを紹介しているサイトをいくつかリストしておきますので、必要に応じて参照ください。
一つ一つ設定するならLambdaを使用しなくてもできるのですが、数が多いときや、新たなインスタンス追加などの場合にもデフォルトで対象に加えたい場合には、Lambdaでやってしまうのが一番シンプルかなと思っています。



