Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

はじめに

本日が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を自動化してみました。
改善余地はたくさんありますし、そのうちアップデートをかけられればいいな、と思います。
以上。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away