#はじめに
本日が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に落とします。これを実装することで、うっかりインスタンスを消し忘れて高額請求、なんてことも避けられますね。
#構成図
こんな感じ。
現在のLambdaはスケジュールベースで起動できますので、それを利用します。
構成要素は以下の3つ。
- 一定時刻に起動して「イベント」の発生を伝えるためのLambda Function ... Function[1]とする
- イベント発生をレシーブして「アクション」を起動するためのSNS Topic
- 対象インスタンスのStart/Stop(または実行の通知)といった「アクション」を実行するLambda Function ... Function[2]とする
※右下にあるSNS Topicはなくても動くので、今回は扱いません。
#手順
- IAM Role の作成
- SNS Topic と Lambda Function の雛形を作成
- SNS Topic にメール通知を実装
- Lambda Function のイベントソース設定
- Lambda Function を実装、テスト
- Lambda の Event Source にタイマーを設定
IAM Roleの作成
今回の構成では2種類のLambda Functionがあるので、それらに適切なIAM Roleを割り当てます。
Function[1]では sns:Publish を、
Function[2]では sns:Publish と ec2:StartInstances , ec2:StopInstances の権限をつけましょう。
今回はLambdaに付与するIAM Roleを作成するので、 Role TypeにはLambdaを選択 しましょう。間違えないように。
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]の設定を行います。
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を自動化してみました。
改善余地はたくさんありますし、そのうちアップデートをかけられればいいな、と思います。
以上。