🥰

特定のタグがついているEC2を自動停止/自動起動する

2021/05/24に公開

この記事の概要

※追記:AWS Systems Manager でも同様のことができるみたいです

intro

検証環境のEC2を自動起動/停止させていた時のこと。
ネットに落ちてるユースケースをコピペして使用していたのですが

  • インスタンスIDがベタ書きで、リソースの増減に対応させる手間
  • 停止用のLambda/起動用のLambdaに分かれており管理が面倒

ということで、結構イライラしてました。
今回は1つのLambdaを使用して、これらをスマートに管理する解決策を紹介します。

今回のポイント・実現したい事

サーバレスの発想の根幹である、ステートレス[1]を目指します。
Lambda自身は何の値も持たず、引数を渡すような形で制御します。
要件は以下の2つ

  • 平日◯時に起動(停止)
  • 対象は〇〇というタグに✕✕という値が入っているインスタンス

構成

構成図(クリックで開く)

用意するもの

  • 制御用のLambda関数と、そのロール
  • 起動に使うタグと、その値
  • 停止に使うタグと、その値
  • 起動用のルール
  • 停止用のルール

EventBridgeのルール作成時にLambdaの指定が必須なので
Lambdaから先に作成するのが望ましいです。

やってみよう

今回のゴール

※ご自身の環境に適宜読み替えて下さい。

1. Lambda関数とそのロールを作成

ロールには

  • AmazonEC2FullAccess
  • CloudWatchFullAccess

をアタッチした物を使用します。

Lambda > 関数 > 関数の作成 > 一から作成

関数名 ランタイム 実行ロール
任意の関数名 Python 3.8 上記で作成したロール
コード(クリックで開く)
lambda_function.py
import boto3
region = 'ap-northeast-1'

def get_target_instance_id(tag_name, tag_value):
    instance_id_list = []    
    ec2 = boto3.client('ec2', region_name=region)
    ec2_data = ec2.describe_instances()
    for ec2_reservation in ec2_data['Reservations']:
        for ec2_instance in ec2_reservation['Instances']:
            ec2_tags = dict([(tag['Key'], tag['Value']) for tag in ec2_instance['Tags']])
            if (ec2_tags.get(tag_name, 'NoSuchTag') == tag_value):
                instance_id_list.append(str(ec2_instance['InstanceId']))
    return instance_id_list

def lambda_handler(event, context):
    print("Target instance has Value [ " + event.get('TagValue') + " ] on Tag [ " + event.get('TagName') + " ]")
    target_instances = get_target_instance_id(event.get('TagName'), event.get('TagValue'))
    ec2 = boto3.client('ec2', region_name=region)

    # start EC2
    if(event.get('CallType') == 'start'):
        ec2.start_instances(InstanceIds=target_instances)
        print('started instances: ' + str(target_instances))

    # stop EC2
    elif(event.get('CallType') == 'stop'):
        ec2.stop_instances(InstanceIds=target_instances)
        print('stopped instances: ' + str(target_instances))

    # other
    else:
        print("invalid CallType")
        print(event)

2. EventBridgeルールの作成

Amazon EventBridge > イベント > ルール > ルールを作成

パターンを定義

Cron式(起動) Cron式(停止)
0 0 ? * MON-FRI * 0 9 ? * MON-FRI *
※『平日に09時に起動,18時に停止』の例
詳しくは公式ドキュメントを参照 → ルールのスケジュール式
ターゲット 機能
---- ----
Lambda関数 1で作った関数名

入力の設定(起動)

定数(JSONテキスト)
{ "CallType": "start", "TagName": "AutoStart", "TagValue": "enable"}

入力の設定(停止)

定数(JSONテキスト)
{ "CallType": "stop", "TagName": "AutoStop", "TagValue": "enable"}

以上です!

ex. 手動で動かしてみる

出来たら動かしてみましょう。
cronでEventBridgeが起動するのを待つのは面倒なので、
テストイベントをいい感じに書いてあげましょう。

outro

基本的に、一回設定したらcron式しか弄らないので
例外処理はダルくて実装してません(汗

今までLambdaの定数をチマチマ書き換えていたのが
インスタンスのタグの値を書き換えるだけで済むのでめっちゃ楽です。
検証環境を建てる時は、EC2と一緒にこのEventBridgeもCFnで作ったらより楽になる予感がしますね。


参考にした記事:AWS LambdaからPythonでEC2のインスタンスを起動するスケジュールを設定してみた

脚注
  1. ステートレスとは、システムが現在の状態を表すデータなどを保持せず、入力の内容によってのみ出力が決定される方式。(IT用語辞典 e-wordsより) ↩︎

Discussion