目次
はじめに
こんにちはディーネットのタナミです。
皆さんは検証環境など不要なEC2やRDSの停止は行っていますでしょうか?
私は使う時だけ起動させるようにしていますが、それでも毎回忘れずに停止させるのは厳しいところがあります。
正直EC2を止め忘れたところで1日に請求される料金はたかが知れてるんじゃない?と思う方もいらっしゃるかもしれません。
ですがそれは大きな落とし穴です。実際に一か月間EC2を付けっぱなしにした料金と毎日8時間だけ起動させた料金を比べてみましょう。
下図のようになんと16.46$もの差があります。
プライシングカリキュレーターは使用率を下げるとその分のEBSが削除された想定で計算するので、実際の金額差はもう少し縮まると思いますが
それでも毎月映画を1本観れるくらいの料金にはなるかと思います。

では毎月の映画の為に...ではなく月々の料金を抑える為にEC2やRDSが指定の時間に自動停止出来るように
LambdaでEC2とRDSの自動停止をセットアップしましょう。
IAMロール作成
まずはLambdaにアタッチするIAMロールの作成を行いましょう。
今回はEC2とRDSの停止を行うので下記のようにポリシーを設定します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EC2Permissions",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeTags",
"ec2:StopInstances"
],
"Resource": "*"
},
{
"Sid": "RDSPermissions",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:ListTagsForResource",
"rds:StopDBInstance",
"rds:StopDBCluster"
],
"Resource": "*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
Lambda作成
IAMロールの作成が出来たので次にLambdaを作成を行います。
ランタイムはPython3.13を実行ロールは既存のロールから先ほど作成したIAMロールを選択します。

また、設定タブからタイムアウトを30秒へ変更します。
これはタイムアウトによるLambdaの実行エラーを防ぐためのものになります。

Lambdaコード
次にLambda用のコードを設定します。
下記のコードではTag:autostopがアタッチされているEC2とRDSを停止する仕組みになっています。
import boto3
import json
from datetime import datetime
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
rds = boto3.client('rds')
stopped_resources = {
'ec2_instances': [],
'rds_instances': []
}
try:
# EC2インスタンスの処理
print("=== EC2インスタンスの処理開始 ===")
ec2_response = ec2.describe_instances(
Filters=[
{'Name': 'tag-key', 'Values': ['autostop']},
{'Name': 'instance-state-name', 'Values': ['running']}
]
)
ec2_instance_ids = []
for reservation in ec2_response['Reservations']:
for instance in reservation['Instances']:
ec2_instance_ids.append(instance['InstanceId'])
print(f"対象EC2インスタンス: {instance['InstanceId']}")
if ec2_instance_ids:
ec2_stop_response = ec2.stop_instances(InstanceIds=ec2_instance_ids)
stopped_resources['ec2_instances'] = ec2_instance_ids
print(f"停止したEC2インスタンス: {ec2_instance_ids}")
else:
print("停止対象のEC2インスタンスが見つかりませんでした")
# RDSインスタンスの処理
print("=== RDSインスタンスの処理開始 ===")
rds_response = rds.describe_db_instances()
rds_instance_ids = []
for db_instance in rds_response['DBInstances']:
db_instance_identifier = db_instance['DBInstanceIdentifier']
db_instance_status = db_instance['DBInstanceStatus']
if db_instance_status == 'available':
try:
db_arn = db_instance['DBInstanceArn']
tags_response = rds.list_tags_for_resource(ResourceName=db_arn)
has_autostop_tag = any(tag['Key'] == 'autostop' for tag in tags_response['TagList'])
if has_autostop_tag:
rds_instance_ids.append(db_instance_identifier)
print(f"対象RDSインスタンス: {db_instance_identifier}")
except Exception as tag_error:
print(f"RDSインスタンス {db_instance_identifier} のタグ取得でエラー: {str(tag_error)}")
continue
# RDSインスタンスの停止
if rds_instance_ids:
for rds_id in rds_instance_ids:
try:
print(f"RDSインスタンス {rds_id} を停止中...")
rds.stop_db_instance(
DBInstanceIdentifier=rds_id
)
stopped_resources['rds_instances'].append(rds_id)
print(f"停止したRDSインスタンス: {rds_id}")
except Exception as rds_stop_error:
print(f"RDSインスタンス {rds_id} の停止でエラー: {str(rds_stop_error)}")
# エラーの詳細をログに出力
import traceback
print(traceback.format_exc())
continue
else:
print("停止対象のRDSインスタンスが見つかりませんでした")
# 結果まとめ
total_stopped = len(stopped_resources['ec2_instances']) + len(stopped_resources['rds_instances'])
if total_stopped > 0:
return {
'statusCode': 200,
'body': json.dumps({
'message': f'合計{total_stopped}個のリソースを停止しました',
'stopped_ec2_instances': stopped_resources['ec2_instances'],
'stopped_rds_instances': stopped_resources['rds_instances']
}, ensure_ascii=False)
}
else:
return {
'statusCode': 200,
'body': json.dumps({
'message': '停止対象のリソースはありませんでした'
}, ensure_ascii=False)
}
except Exception as e:
print(f"エラーが発生しました: {str(e)}")
import traceback
print(traceback.format_exc())
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)}, ensure_ascii=False)
}
実行テスト
次にテストタブからLambdaが正常に動作するかテストします。
下図のような結果が帰ってくれば成功です。

EventBridge設定
無事Lambdaの動作を確認出来たので、最後に指定の時間にLambdaが自動的に動作するように設定します。
トリガーを追加から下図のようにEventBridgeを設定します。

また、cronはcron(分 時 日 月 曜日 年)のように表すことが出来ます。
EventBridgeはUTCで時間を設定するので、日本時間の19時に動かしたい場合は下記のように設定します。
cron(0 10 * * ? *)
おまけ
Lambdaの料金について疑問に思った方もいらっしゃるかもしれません。
ですが今回のLambdaは無料利用枠の範囲内に収まっているためLambdaの実行料金はかかりません。
今回のLambdaの1ヶ月の実行回数と実行時間は下記のようになります。
実行回数:毎日1回 × 30日 = 30回/月
実行時間:6秒 × 30日 = 180秒/月
GB秒 : 0.125GB × 180秒 = 22.5GB秒/月
下図の無料利用枠に収まる計算になります。

まとめ
今回は月々の料金を抑える為にLambdaでEC2とRDSの自動停止を設定しました。
ただし、今回の設定では自動停止のみとなっているので、起動は手動で行う必要があります。
より便利にしたい場合は、起動用のLambdaも設定すると良いかもしれません。
最後に注意点として、RDSの場合は停止期間が7日を超えると自動的に起動してしまうので
長期間使わない場合はスナップショットを取得してRDSを削除することも検討してください。
よろしくお願いします。