AWS LambdaでEC2インスタンスのStart/Stopを自動化してみる

  • 7
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

本日がAdvent Calendarデビュー戦のhassaku_63です。普段は飯田橋にいます。
25日が最後とは知らず、うっかり同期につられて登録してしまい、今年のトリを飾ることとなってしまいました。
聖なる夜に私はひとり寂しくこの記事の執筆と格闘しております。。。

カレンダーのヘッドライン(?)にはバックアップの自動化、と書いていますがごめんなさい、嘘です。
今日はEC2のStart/Stopを自動化する実装の一例をご紹介します。

SlideShareにスライドをUpしています。こちらも併せてご参照あれ。

背景

今回のネタ、そもそもは「サーバーレスアーキテクチャ」「Cloud Automator」「re:Invent」の3つが発端です。

サーバーレスアーキテクチャ

話題になってますね。サーバーレスアーキテクチャ。
サーバーの運用から解放されるなんて、ステキですよね。

Cloud Automator

Cloud Automatorはバックアップなどのオペレーションを自動化するSaaSツールです。サーバーワークスが提供しています。

Cloud Automatorでできること、その典型的なユースケースとしては↓のようなものがあります。

  • EC2インスタンスを業務時間の間だけStartさせる
  • EBSやRDSのバックアップを自動で

※ステマしても良いのですが、今回の目的はそこじゃないので商品紹介とかはしません。Cloud Automatorはあくまでネタのキッカケですので、あしからず

re:Invent

「Python対応」と「スケジュールベースでの実行」が発表されましたね。
このニュースにテンションの上がった人も多いはず。
せっかくならこの2つを盛り込んだ何かを作りたい、と思っていました。

今回やること

↑で紹介したCloud Automatorのユースケースの1つ、「EC2インスタンスのStart/Stopの自動化」をLambdaで実装してみます。
10:00にインスタンスを起動し、19:00に落とします。これを実装することで、うっかりインスタンスを消し忘れて高額請求、なんてことも避けられますね。

構成図

こんな感じ。
構成図.png
現在のLambdaはスケジュールベースで起動できますので、それを利用します。
構成要素は以下の3つ。

  • 一定時刻に起動して「イベント」の発生を伝えるためのLambda Function ... Function[1]とする
  • イベント発生をレシーブして「アクション」を起動するためのSNS Topic
  • 対象インスタンスのStart/Stop(または実行の通知)といった「アクション」を実行するLambda Function ... Function[2]とする

※右下にあるSNS Topicはなくても動くので、今回は扱いません。

手順

  1. IAM Role の作成
  2. SNS Topic と Lambda Function の雛形を作成
  3. SNS Topic にメール通知を実装
  4. Lambda Function のイベントソース設定
  5. Lambda Function を実装、テスト
  6. Lambda の Event Source にタイマーを設定

IAM Roleの作成

今回の構成では2種類のLambda Functionがあるので、それらに適切なIAM Roleを割り当てます。
Function[1]では sns:Publish を、
Function[2]では sns:Publishec2:StartInstances , ec2:StopInstances の権限をつけましょう。

今回はLambdaに付与するIAM Roleを作成するので、 Role TypeにはLambdaを選択 しましょう。間違えないように。
lambda_role-type.png

SNS Topic と Lambda Function の雛形を作成

外側だけ先に作ってしまいます。
Function[1]を2つ(起動/停止時刻の発火用)、Function[2](起動/停止の実行用)を1つ作成し、SNS Topicを最低1つ作成します。

Functionのコードは適当なテンプレートでも埋め込んでおきましょう。
IAM Roleの割り当ては先ほど作成したものを適用します。

Topicの Subscriptionsには、Function[1]のArnを登録 しておいてあげましょう。

SNS Topic にメール通知を実装

上で作ったTopicに、お好みで自分のメールアドレスも登録してあげると動作確認に使えます。
これは最悪なくても動きます。
Create Subscriptionにてメールアドレスを入力した後、"Confirm Subscription"すると確認用メールが行きます。文面に記載されたリンクを踏むとSubscription登録は完了です。

Lambda Function のイベントソース設定

Lambda Functionのイベントソースを設定します。
まず時刻ベースで起動するFunction[1]の設定を行います。
lambda-function_event-source.png
Cronライクな書き方ができます。時刻は今のところUTCのみ。JSTの10:00、平日に起動したかったら上の図のようになります。

Function[2]も同じ要領です。
SNSのTopicを選ぶだけ。設定時に候補となるTopicをリストしてくれます。

Lambda Functionを実装、テスト

コードの一例を挙げます。


# EC2起動時刻を知らせるためのLambda Functionコードの例
import json
import boto3

sns_client = boto3.client("sns")

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))

    # publish to SNS Topic ... 
    topic_arn = "my-topic-arn"
    message = dict(Action="start", InstanceIds=["instance-id"])
    param = dict(TopicArn=topic_arn, Subject="subject", Message=json.dumps(message))

    return sns_client.publish(**param)

"どのインスタンス" が "どのアクション(Start/Stop)"を実行すべきか、という情報をTopicに対して渡しています。パラメータの仕様は

    message = dict(Action="start", InstanceIds=["instance-id"])

ここの記述が全てです。インスタンスID直打ちのコードなのが非常にナンセンスですが...気にせず行きましょう。
お次は実際にアクション(インスタンスのStart/Stop)を実行するFunctionのコード例です。

import json
import boto3

ec2_client = boto3.client("ec2")

def lambda_handler(event, context):
    # Get parameter
    param = json.loads(event["Records"][0]["Sns"]["Message"])

    # Invoke action
    if param.get("Action", "") == "start":
        ret = ec2_client.start_instances(InstanceIds=param.get("InstanceIds", []))
    elif param.get("Action", "") == "stop":
        ret = ec2_client.stop_instances(InstanceIds=param.get("InstanceIds", []))

    return ret

パラメータの取得方法が汚いとか言わないで。。
Message に入っているパラメータを見て、適切なインスタンスをStart or Stopさせます。
こんな感じで実装は完了です。

課題とか反省点とか

いろいろありますが特に気になってることを挙げます。全部改善できれば現場投入しても使い物になるかも。

インスタンスの指定が直打ち

必要なパラメータは外部から引っ張れるようにすべきですね。せめてタグ指定とかにしておけばよかったか。

汎用性を高める工夫としては、
『「今回作成したLambda Function」を作成するLambda Function』を実装して、そのFunctionをAPI Gatewayでラップして、POSTのパラメータで対象インスタンスを指定させるようにする・・・という手が考えられます。メタプログラミング的な。これはいつかやりたい。

コード汚い

エラーハンドリングとか何も考慮してないですし。

SNSのMessageについて

パラメータに渡す段階で一度JSONを文字列に変換してますが、確かJSONオブジェクトのまま渡せる仕様のはず。この辺は勉強不足です。

まとめ

Lambdaを使ってEC2のStart/Stopを自動化してみました。
改善余地はたくさんありますし、そのうちアップデートをかけられればいいな、と思います。
以上。

この投稿は 今年もやるよ!AWS Lambda縛り Advent Calendar 201525日目の記事です。