スケジュールに従ってEC2を自動起動・自動停止する（Lambda編）

# IAMロールの作成

Lambda関数の新規作成画面からカスタムロールを新規作成できなくなっているようなので、あらかじめIAMのメニューからロール、ポリシーを作成します。以下の権限が必要となります。

- LambdaからEC2を停止/起動するための権限
- CloudWatchLogsにログを出力するための権限

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/936d67bb-6d01-c643-1188-2692e57ad9ca.png)


ロール作成時、信頼されたエンティティはLambdaを選択し、以下のポリシーで作成します。通常は`ec2:DescribeInstances`の権限は不要ですが、今回はNameタグをキーにインスタンスIDを取得してからEC2を停止するので付与しています。

```json
{
    "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ロールを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/70512970-5559-568a-ed4d-d7630d0f7160.png)

Lambdaが作成できたら`lambda_function.py`のコードを以下のように修正して「Deploy」ボタンを押して保存します。
`print`の2行はログに出力させるためのものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/c6e6d0f7-b629-9d0d-65dc-975966fb0a8f.png)

```python
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`関数が呼ばれるようになっています。ランタイム設定のハンドラを見ればわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/56d7ded6-226c-b7b8-a4e3-ae9d3b21956f.png)

なので、初めからある`lambda_function.py`のファイル名を変えてしまうと中身のコードが正しくても動かなくなりますし、コードは`lambda_handler`関数の中に記述する必要があります。`def lambda_handler(event, context):`の1行はお決まりのコードだと理解しておけば問題なさそう。

[Python の AWS Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html)

また、`def lambda_handler(event, context):`の第1引数の**event**でイベントを渡しています。
コードの中で利用する値をJSON形式で渡すことができるようです。今回のコードでいうと、以下の2か所をJSONで渡すことにしています。（こうすることで、コードに直接パラメータを埋め込む必要がなくなり汎用性が高まるのだと理解）

- `event["InstanceName"]`
- `event["Action"]`

InstanceNameはNameタグの値、Actionはstart/stopのいずれかを渡す想定です。
このあたりは、クラスメソッドさんの以下の記事を参考にしています。

[できるだけシンプルな仕組みで簡単にEC2の自動起動・停止を実現したい！](https://dev.classmethod.jp/articles/simple-auto-start-stop-for-ec2/)

# Lambda関数の実行

「Test」ボタンを押すとJSONで渡す値（イベント）を指定する画面になります。
イベント名は「StartStopEC2」として、JSONの中身は以下のように書き換えて作成します。
まずは起動(start)から。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/f2e34013-85d9-7ee4-467d-925c2711705f.png)

```json
{
  "Action": "start",
  "InstanceName": "AmazonLinux01"
}
```

テストイベントが作成できたらもう一度「Test」ボタンを押すと関数が実行されます。
Function Logsを見ると、もともと`stopped`だったのが`pending`になっていることがわかります。
EC2の画面を見てみると「実行中」に変わっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/eaef3cee-191c-fd5e-b8ab-ce9862889b8f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/6d89f9b1-9691-22b0-ea30-f4c823e916bf.png)

Test → Configure test eventからテストイベントの設定を開いて、今度は`stop`に変更して保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/22fbceb2-e9c5-982b-d1ab-90c07ef75c3a.png)

再度テストを実行してみると、今度はもともと`running`だったのが`stopping`になっていることがわかります。
EC2の画面では「停止中」になっていて、しばらくすると「停止済み」となりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/0b839327-3d48-eb53-15e7-eb9dc2488aa5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/75e5d112-0c69-6b9a-252f-ccd879e59d70.png)

# CloudWatch Eventsにスケジュールを登録

スケジュールに従ってLambda関数を実行するにはCloudWatch Eventsを利用します。CloudWatchのメニューの中にあるイベント → ルールから登録できます。とりあえずの動作確認が目的なので、スケジュールは5分おきに実行するように指定しています。cronの書き方は以下の公式ドキュメントを見ればだいたいわかります。

[ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/3d32d597-68be-fc15-473e-840f3d14320b.png)

ターゲットの入力の設定では「定数(JSON テキスト)」を選択し、ActionとInstanceNameの項目を入れたJSONを渡してあげます。以下は、EC2を起動させる場合のJSONですが、テストイベントの時と同じように`start`を`stop`に変えてあげればEC2を停止するルールを作成することができます。

```json
{"Action": "start", "InstanceName": "AmazonLinux01"}
```

つまり、ルールを2つ作成して、EC2起動は平日の朝の9時、EC2停止は平日の20時といった形で設定することになります。

ルールを登録してしばらく待つと、停止済みだったEC2が起動してきました。
ログもCloudWatch Logsに出力されていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/e952ec77-4897-fa4b-4fe7-dbcc1fb3be27.png)

# まとめ

python(boto3)でコードを書くのが敷居が高いイメージがあり、なかなかちゃんと触る機会がなかったのですが、やってることはWindowsのタスクスケジュラやLinuxのcronと変わりはないので、基本的な使い方を覚えてしまえばサーバレスならではのメリットを享受できそうだと感じました。

ここまで書いておきながらなんですが、EC2起動/停止はLambdaでコードを書かなくてもSSM Automationで実現できるので、また試してみて投稿したいと思います！

# 参考リンク
[Python の AWS Lambda 関数ハンドラー](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html)

[できるだけシンプルな仕組みで簡単にEC2の自動起動・停止を実現したい！](https://dev.classmethod.jp/articles/simple-auto-start-stop-for-ec2/)

[ルールのスケジュール式](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html)