michimani.net

CloudWatch Events + Lambda で 特定のタグを持つ EC2 インスタンスを自動で起動・停止させる

2018-12-14

AWS を個人の勉強用で使っていたり、開発環境で使っていたりする場合、少しでもランニングコストを抑えるために休日や夜間にはインスタンスを停止したいときがあります。これを自動でやります。 この手の方法は既に色々出回っていますが、自分なりにしっくりくるものができたので書いておきます。

※追記

AWS CDK を使ってこれらの環境を構築する方法についても書きました。

概要

特定のタグを持つインスタンスのみを対象に、自動起動・停止を実行します。

使うサービスは、以下の2つです。

CloudWatch は色々監視するために使うイメージがありますが、その中にある CloudWatch Events は、cron のような感じで決められたスケジュールに従って処理を実行できます。

やること

  1. Lambda で自動起動・停止のスクリプトを書く
  2. CloudWatch Events で起動・停止のイベントスケジュールを設定する

1. Lambda で自動起動・停止のスクリプトを書く

特定のタグと値を持つインスタンスを対象に、自動起動・停止を実行します。

import json
import boto3
import traceback

def lambda_handler(event, context):
    try:
        region = event['Region']
        action = event['Action']
        client = boto3.client('ec2', region)
        responce = client.describe_instances(Filters=[{'Name': 'tag:AutoStartStop', "Values": ['TRUE']}])

        target_instans_ids = []
        for reservation in responce['Reservations']:
            for instance in reservation['Instances']:
                target_instans_ids.append(instance['InstanceId'])

        print(target_instans_ids)

        if not target_instans_ids:
            print('There are no instances subject to automatic start / stop.')
        else:
            if action == 'start':
                client.start_instances(InstanceIds=target_instans_ids)
                print('started instances.')
            elif action == 'stop':
                client.stop_instances(InstanceIds=target_instans_ids)
                print('stopped instances.')
            else:
                print('Invalid action.')

        return {
            "statusCode": 200,
            "message": 'Finished automatic start / stop EC2 instances process. [Region: {}, Action: {}]'.format(event['Region'], event['Action'])
        }
    except:
        print(traceback.format_exc())
        return {
            "statusCode": 500,
            "message": 'An error occured at automatic start / stop EC2 instances process.'
        }

次の部分で、特定のタグを持つ EC2 インスタンスのリストを取得しています。

responce = client.describe_instances(Filters=[{'Name': 'tag:AutoStartStop', "Values": ['TRUE']}])

今回は AutoStartStop というタグ名で TRUE という値を持っているインスタンスを取得しています。

スクリプト内に出てくる event は CloudWatch Events から この Lambda 関数を実行するときに渡すパラメータです。
パラメータは JSON 文字列で指定できるので、対象となるリージョンと起動/停止のアクションを受け取るようにしています。


ソースは gist に置いています。

このスクリプトの実行に必要なロール

この関数を実行するためには、以下のようなロールを使います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}

重要なのは EC2 に関する権限です。 リストを取得する権限 (ec2:DescribeInstances) と、インスタンスを起動・停止する権限 (ec2:StartInstances, ec2:StopInstances) が必要です。

2. CloudWatch Events で起動・停止のイベントスケジュールを設定する

CloudWatch > イベント ルール > ルールの作成 と進んで、スケジュールを設定します。

CloudWatch Events スケジュール

このように設定すると、平日 10:00 に自動起動が実行されることになります。 停止の場合は、ターゲットへの入力 (JSON文字列) を {"Region": "ap-northeast-1", "Action": "stop"} とすればよいです。

ひとつ注意が必要なのは、 Cron 式で指定する日時はのタイムゾーンは UTC という点です。

あとは、自動起動・停止したいインスタンスに特定のタグを付ければ完成です。

まとめ

タグベースにしておけば、自動起動・停止の対象にしたいインスタンスが増えても管理しやすいです。
最近では RDS も起動・停止に対応したようなので、これをベースにして作ってみようと思います。

以上、よっしー (michimani) でした。