IAMロールの作成
Lambda関数の新規作成画面からカスタムロールを新規作成できなくなっているようなので、あらかじめIAMのメニューからロール、ポリシーを作成します。以下の権限が必要となります。
- LambdaからEC2を停止/起動するための権限
- CloudWatchLogsにログを出力するための権限
ロール作成時、信頼されたエンティティはLambdaを選択し、以下のポリシーで作成します。通常はec2:DescribeInstances
の権限は不要ですが、今回はNameタグをキーにインスタンスIDを取得してからEC2を停止するので付与しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:DescribeInstances"
],
"Resource": "*"
}
]
}
Lambda関数の作成
ランタイムはPython3.8、実行ロールは事前に作成したIAMロールを選択します。
Lambdaが作成できたらlambda_function.py
のコードを以下のように修正して「Deploy」ボタンを押して保存します。
print
の2行はログに出力させるためのものです。
import boto3
def lambda_handler(event, context):
# valiables
instance_name = event["InstanceName"]
# get instance id
client = boto3.client("ec2")
instance_id = client.describe_instances(
Filters=[{"Name": "tag:Name","Values": [instance_name]}]
)["Reservations"][0]["Instances"][0]["InstanceId"]
# start / stop ec2
if event["Action"] == "start":
response = client.start_instances(InstanceIds=[instance_id])
elif event["Action"] == "stop":
response = client.stop_instances(InstanceIds=[instance_id])
print(event)
print(response)
全然知らなかったのですが、Lambdaではデフォルトではlambda_function.py
のlambda_handler
関数が呼ばれるようになっています。ランタイム設定のハンドラを見ればわかります。
なので、初めからあるlambda_function.py
のファイル名を変えてしまうと中身のコードが正しくても動かなくなりますし、コードはlambda_handler
関数の中に記述する必要があります。def lambda_handler(event, context):
の1行はお決まりのコードだと理解しておけば問題なさそう。
また、def lambda_handler(event, context):
の第1引数のeventでイベントを渡しています。
コードの中で利用する値をJSON形式で渡すことができるようです。今回のコードでいうと、以下の2か所をJSONで渡すことにしています。(こうすることで、コードに直接パラメータを埋め込む必要がなくなり汎用性が高まるのだと理解)
event["InstanceName"]
event["Action"]
InstanceNameはNameタグの値、Actionはstart/stopのいずれかを渡す想定です。
このあたりは、クラスメソッドさんの以下の記事を参考にしています。
できるだけシンプルな仕組みで簡単にEC2の自動起動・停止を実現したい!
Lambda関数の実行
「Test」ボタンを押すとJSONで渡す値(イベント)を指定する画面になります。
イベント名は「StartStopEC2」として、JSONの中身は以下のように書き換えて作成します。
まずは起動(start)から。
{
"Action": "start",
"InstanceName": "AmazonLinux01"
}
テストイベントが作成できたらもう一度「Test」ボタンを押すと関数が実行されます。
Function Logsを見ると、もともとstopped
だったのがpending
になっていることがわかります。
EC2の画面を見てみると「実行中」に変わっていました。
Test → Configure test eventからテストイベントの設定を開いて、今度はstop
に変更して保存します。
再度テストを実行してみると、今度はもともとrunning
だったのがstopping
になっていることがわかります。
EC2の画面では「停止中」になっていて、しばらくすると「停止済み」となりました。
CloudWatch Eventsにスケジュールを登録
スケジュールに従ってLambda関数を実行するにはCloudWatch Eventsを利用します。CloudWatchのメニューの中にあるイベント → ルールから登録できます。とりあえずの動作確認が目的なので、スケジュールは5分おきに実行するように指定しています。cronの書き方は以下の公式ドキュメントを見ればだいたいわかります。
ターゲットの入力の設定では「定数(JSON テキスト)」を選択し、ActionとInstanceNameの項目を入れたJSONを渡してあげます。以下は、EC2を起動させる場合のJSONですが、テストイベントの時と同じようにstart
をstop
に変えてあげればEC2を停止するルールを作成することができます。
{"Action": "start", "InstanceName": "AmazonLinux01"}
つまり、ルールを2つ作成して、EC2起動は平日の朝の9時、EC2停止は平日の20時といった形で設定することになります。
ルールを登録してしばらく待つと、停止済みだったEC2が起動してきました。
ログもCloudWatch Logsに出力されていました。
まとめ
python(boto3)でコードを書くのが敷居が高いイメージがあり、なかなかちゃんと触る機会がなかったのですが、やってることはWindowsのタスクスケジュラやLinuxのcronと変わりはないので、基本的な使い方を覚えてしまえばサーバレスならではのメリットを享受できそうだと感じました。
ここまで書いておきながらなんですが、EC2起動/停止はLambdaでコードを書かなくてもSSM Automationで実現できるので、また試してみて投稿したいと思います!