InstanceRestarter（インスタンスをリスタートするやーつー）

# 概要

必要にかられて、こんなものを作ったのでメモがてら。
1. EventBridgeから定期的に
1. Lambdaを起動して
    1. 指定された名前に合致するインスタンスに対して
    1. reboot（またはterminate）のリクエストを出す

# 作成手順

## IAM
1. ロールを作成
    1. 信頼されたエンティティを選択
        1. 信頼されたエンティティタイプ → AWSのサービス
        1. ユースケース → Lambda
    1. 許可を追加
        1. 許可ポリシー
            1. （何もしなくてOK）
        1. 許可の境界を設定
            1. 許可の境界なしでロールを作成（デフォルト）
    1. 名前、確認、および作成
        1. ロールの詳細
            1. ロール名 → それなりの名前（`InstanceRestarter-role`等）
            1. 説明 → お好きに
1. 作成したロールを選択
1. 許可タブ内から`許可を追加 → インラインポリシーを作成`を選択
    1. アクセス許可を指定
        1. ポリシーエディタで`JSON`を選択して[`IAM.json`](#iamjson)の内容を貼り付ける
            * rebootではなくterminateをする場合には貼り付け後に、JSON内の`ec2:RebootInstances`を`ec2:TerminateInstances`に書き換える
        1. JSON内の`XXXXXXXXXXXX`をAWSのアカウントIDに書き換える
    1. 確認して作成
        1. ポリシーの詳細
            1. ポリシー名 → それなりの名前（`InstanceRestarter-role-policy`等）

## Lambda
1. `関数の作成`ボタン
    1. 基本的な情報
        1. 関数名 → それなりの名前（`InstanceRestarter-lambda`等）
        1. ランタイム → `Ruby`（最新のサポート対象のものでOK）
        1. アーキテクチャ → `x86_64`
        1. アクセス権限 → デフォルトの実行ロールの変更 → 既存のロールを使用する
        1. 既存のロール → 先ほど作成したIAMを選択
    1. 詳細設定 → すべて未チェック（デフォルト）のまま
1. `コード`タブ
    1. `lambda_function.rb`をダブルクリックで開き、[`lambda_function.rb`](#lambda_functionrb)の内容を貼り付け、`Deploy`ボタン
        * rebootではなくterminateをする場合には貼り付け後に、ソース内の`ec2_client.reboot_instances`を`ec2_client.terminate_instances`に書き換え、`Deploy`ボタン
1. `設定`タブ
    1. 一般設定
        1. タイムアウトを必要に応じて変更する
            * 30秒とか
    1. 環境変数
        1. キー`INSTANCE_NAMES`を追加し、値として対象としたいEC2インスタンスの名前を空白区切りで列挙する

## EventBridge
1. 左メニュー`バス`&gt;`ルール`内の`ルールを作成`ボタン
    1. ルールの詳細
        1. 名前 → それなりの名前（`InstanceRestarter-schedule-rule`等）
        1. 説明 → お好きに
        1. イベントバス → お好きに
        1. 選択したイベントバスでルールを有効にする → ON
        1. ルールタイプ → スケジュール
        1. :bangbang:メインのボタン`EventBridge Scheduler で続行`**ではなく**`続行してルールを作成する`ボタンで次の項目へ:bangbang:
    1. スケジュールを定義
        1. スケジュールパターン → お好きに
            * 毎日3時に定期実行であれば`cron(0 18 * * ? *)`
            * 毎日5時に定期実行であれば`cron(0 20 * * ? *)`
    1. ターゲットを選択
        1. ターゲットタイプ → AWSのサービス
        1. ターゲットを選択 → Lambda関数
        1. 関数 → 先ほど作成したLambdaを選択
    1. バージョン/エイリアスを設定
        * デフォルト
    1. 追加設定
        * すべてデフォルトのまま

# ログ
（Lambdaが実行された後に）CloudWatch Logsのロググループ`/aws/lambda/{Lambda関数名}`に出力される。
必要に応じてロググループの保持期間を設定する。
内容としては以下の通り
* 指定された名前のインスタンスが見つからないとき
    * `#{name}: not found.`
* 指定された名前のインスタンスに対しリスタート要求を出したとき
    * `#{name}: restart #{instance_id}`
    * 同じ名前で複数のインスタンスがあれば複数行出力される

# ソース一覧

## IAM.json
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:RebootInstances"
            ],
            "Resource": [
                "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXXX:instance/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}
```

## lambda_function.rb
```ruby
require 'aws-sdk-ec2'
require 'json'

def lambda_handler(event:, context:)
  instance_names = ENV['INSTANCE_NAMES'].split
  
  ec2_client = Aws::EC2::Client.new()
  
  instance_names.each do |name|
    describe_instances = ec2_client.describe_instances({
      filters: [
        {
          name: 'tag-key',
          values: ['Name'],
        },
        {
          name: 'tag-value',
          values: [name],
        },
      ]
    })
    
    if describe_instances.reservations.empty? then
      puts "#{name}: not found."
      next
    end
    
    describe_instances.reservations[0].instances.each do |item|
      puts "#{name}: restart #{item.instance_id}"
      ec2_client.reboot_instances({
        instance_ids: [
          item.instance_id,
        ],
      })
    end
  end
  
  { statusCode: 200, body: 'finish.' }
end
```