EC2とRDSを自動停止!AWSコスト削減のためのスクリプトを紹介
AWSのEC2やRDS、検証環境でついつい止め忘れて、予想外の請求額にヒヤッとした経験はありませんか?
そんな悩みを解決するために、 EC2とRDSを自動で停止するスクリプト を作成しました!
このスクリプトを使えば、毎晩決まった時間にEC2とRDSを自動で停止してくれるので、コスト削減と運用効率の向上に繋がります。
スクリプトの特徴
- 簡単設定: Cloudshellで数コマンドを実行するだけで、全リージョンまたは特定リージョンにスクリプトをデプロイできます。
- 停止設定: 日本時間の午後9時から午前8時まで、1時間おきに停止スクリプトを実行し、起動中のEC2とRDSを確実に停止します。
- 停止除外設定: 特定のEC2やRDSを常時稼働させたい場合は、タグを設定するだけで停止対象から除外できます。
スクリプトの使い方
- ファイルをダウンロード: 以下のコマンドをCloudshellで実行して、スクリプトをダウンロードします。
wget https://iret.media/wp-content/uploads/2025/02/Automatic-Stop-System.zip
- ファイルを解凍:
unzip Automatic-Stop-System.zip
- ディレクトリに移動:
cd Automatic-Stop-System
- スクリプトを実行:
- 全リージョンにデプロイする場合:
python Deploy_All_Region.py
- 特定のリージョンにデプロイする場合:
python Deploy_Individual_Region.py
- デプロイしたスタックを削除する場合:
python Delete_All_Region.py
- 全リージョンにデプロイする場合:
- 特定のEC2・RDSを停止から除外:
停止したくないEC2・RDSインスタンス・RDSクラスターに、以下のタグを追加します。- キー:
Always_Running_Reason
- 値: 常時起動する理由を英語で記載 (例:
For monitoring
)
- キー:
構成要素
このスクリプトは、以下の要素で構成されています。
- デプロイ用Pythonスクリプト: CloudFormationスタックの作成・更新、Lambda関数のデプロイを自動化します。
- CloudFormationテンプレート: Lambda関数、IAMロール、CloudWatch EventsルールなどのAWSリソースを定義します。
- Lambda関数: EC2インスタンス、RDSクラスタ、RDSインスタンスを停止する処理を実行します。
●デプロイ用Pythonスクリプトの説明
デプロイ用Pythonスクリプトのフローチャートは以下となります。S3からLambda関数をデプロイする場合、LambdaとS3は同一リージョンに存在する必要があるため、デプロイするリージョンごとにS3バケットを作成しています。
作成されたS3バケットのバケット名とキー情報はCfnのパラメータとしてデプロイ時に参照されます。
●CFnテンプレート及びLambda関数の説明
CFnテンプレートでは、各リージョンに「EventBridge Scheduler」と「Lambda関数」をデプロイするシンプルな構成としています。
AWSTemplateFormatVersion: '2010-09-09' Description: 'Automatic-Stop-System-Deploy' Parameters: TempS3BucketName: Type: String TempS3KeyName: Type: String Resources: LambdaFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: !Sub '${AWS::Region}-Automatic-Stop-System-Lambda-Policy' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:DescribeInstances - ec2:StopInstances - rds:DescribeDBClusters - rds:StopDBCluster - rds:DescribeDBInstances - rds:StopDBInstance Resource: '*' LambdaFunction: Type: AWS::Lambda::Function Properties: Handler: index.lambda_handler Code: S3Bucket: !Sub '${TempS3BucketName}' S3Key: !Sub '${TempS3KeyName}' Role: !GetAtt LambdaFunctionRole.Arn Runtime: python3.11 MemorySize: 128 Timeout: 600 LogGroupFirehosLambda: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub '/aws/lambda/${LambdaFunction}' RetentionInDays: 7 LambdaFunctionPermission: Type: "AWS::Lambda::Permission" Properties: Action: "lambda:InvokeFunction" FunctionName: !GetAtt - LambdaFunction - Arn Principal: "events.amazonaws.com" SourceArn: !GetAtt - StopScheduleEvent - Arn StopScheduleEvent: Type: AWS::Events::Rule Properties: Description: ’Stop schedule event for lambda’ ScheduleExpression: 'cron(0 12-23 * * ? *)' State: ENABLED Targets: - Arn: !GetAtt LambdaFunction.Arn Id: ScheduleEvent1Target DependsOn: - LambdaFunction
Lambda関数では対象リソースに設定されたタグ情報を読み込み、除外対象のリソースの場合は処理をスキップさせます。
import boto3, json from botocore.exceptions import ClientError client_EC2 = boto3.client('ec2') client_RDS = boto3.client('rds') def Stop_EC2_instances(): try: response = client_EC2.describe_instances(Filters=[{'Name': 'instance-state-name','Values': ['running']}]) except ClientError as e: print('boto3 client error: %s' % e) return for reservation in response['Reservations']: flag = False for tag in reservation['Instances'][0]['Tags']: if(tag['Key'] == 'Always_Running_Reason' and tag['Value']): flag = True if flag: continue try: response = client_EC2.stop_instances(InstanceIds=[reservation['Instances'][0]['InstanceId']]) except ClientError as e: print('boto3 client error: %s' % e) continue def Stop_DB_Cluster(): try: response = client_RDS.describe_db_clusters() except ClientError as e: print('boto3 client error: %s' % e) return for DBCluster in response['DBClusters']: if not DBCluster['Status'] == "available": continue if not "aurora" in DBCluster['Engine']: continue flag = False for tag in DBCluster['TagList']: if(tag['Key'] == 'Always_Running_Reason' and tag['Value']): flag = True if flag: continue try: response = client_RDS.stop_db_cluster(DBClusterIdentifier=DBCluster['DBClusterIdentifier']) except ClientError as e: print('boto3 client error: %s' % e) continue def Stop_DB_Instance(): try: response = client_RDS.describe_db_instances() except ClientError as e: print('boto3 client error: %s' % e) return for DBInstance in response['DBInstances']: if not DBInstance['DBInstanceStatus'] == "available": continue if "aurora" in DBInstance['Engine']: continue flag = False for tag in DBInstance['TagList']: if(tag['Key'] == 'Always_Running_Reason' and tag['Value']): flag = True if flag: continue try: response = client_RDS.stop_db_instance(DBInstanceIdentifier=DBInstance['DBInstanceIdentifier']) except ClientError as e: print('boto3 client error: %s' % e) continue def lambda_handler(event, content): Stop_EC2_instances() Stop_DB_Cluster() Stop_DB_Instance() return { 'statusCode': 200,'body': json.dumps('Excuted!')}
このスクリプトで、AWSコストの削減と運用効率の向上を実現しましょう!