EC2インスタンスの自動停止起動

# 経緯
EC2インスタンスを、使用しない時間帯のみ止めてコスト削減をしたい。
Lambdaのみでの実装と、Instance Schedulerでの実装を行い、それぞれの複数インスタンス・複数スケジュール設定の場合やコスト、注意などをまとめる。

 **【参考】** 

https://aws.amazon.com/jp/premiumsupport/knowledge-center/start-stop-lambda-cloudwatch/

https://aws.amazon.com/jp/builders-flash/202110/instance-scheduler/?wsf.filter-name=*all

https://docs.aws.amazon.com/ja_jp/solutions/latest/instance-scheduler/deployment.html#step5

# Lambda のみで実装する

 **〇手動方式〇** 

 **1. IAM ポリシーと IAMロール作成** 

* ポリシーの作成。CloudWatch Logの書き込み、EC2書き込み権限保有（以下JSON）

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Start*",
        "ec2:Stop*"
      ],
      "Resource": "*"
    }
  ]
}
```
* 上記ポリシーをアタッチしたロールを作成

 **2. EC2 インスタンスを停止および起動する Lambda 関数の作成** 

* AWS Lambda コンソールで、「関数の作成」 クリック
* 「一から作成」クリック
* [基本的な情報] に次の事項を追加
* [関数名] に EC2 インスタンスを開始／停止させる関数の名前を入力

[ランタイム] ： [python 3.8] を選択
[アクセス権限] ： [デフォルトの実行ロールの変更] を展開
[実行ロール] ： [既存のロールを使用する] を選択
1で作成したロールを登録

* 「関数の作成」
* 「コード」でlambda_functionに以下貼り付け

上の手順で開始させる関数と停止させる関数をそれぞれ作成し、以下を記述する

**インスタンスの開始**

```
import boto3
region = 'ap-northeast-1'
instances = ['InstanceID']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.start_instances(InstanceIds=instances)
    print('started your instances: ' + str(instances))
```

**インスタンスの停止**

```
import boto3
region = 'ap-northeast-1'
instances = ['InstanceID']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
ec2.stop_instances(InstanceIds=instances)
    print('stopped your instances: ' + str(instances))
```

 **3. CloudWatch Events（Eventblidge） のルールを作成** 

* Amazon CloudWatch （Eventblidge）コンソールを開く
* 左ナビゲーションペインの [イベント] の下にある [ルール] をクリック
* [ルールの作成] を選択
* [イベントソース] の [スケジュール] をクリック
* [Cron expression] に、Lambda にインスタンスを起動/停止させる時刻を表すスケジュール式を入力（UTC）
* [ターゲットの追加]  をクリック
* [Lambda関数] でEC2 インスタンスを停止させる関数(Start,Stop)を選択
* [詳細の設定] を選択、名前など設定
* 「ルールの作成」
※StartとStopそれぞれ作成

設定した時間になり、スケジュール通り起動・停止していることを確認


 **〇テンプレートを使用〇** 

*  [AWS CloudFormation テンプレート](https://aws.amazon.com/jp/premiumsupport/knowledge-center/start-stop-lambda-cloudwatch/) を YAML ファイルとして保存
* AWS CloudFormationより、スタックを作成して上記のテンプレートを使用
* 名前や対象インスタンスID、スケジュールをパラメータに設定し、「作成」
* 以下のLambda関数とCloudwatchルールが作成

Lambda関数
・TestEC2Scheduler-StartEC2Instances-＊
・TestEC2Scheduler-StopEC2Instances-＊

CloudWatchルール
・TestEC2Scheduler-StartScheduledRule-＊
・TestEC2Scheduler-StopScheduledRule- ＊

設定した時間になり、スケジュール通り起動・停止していることを確認

## 複数インスタンス・複数スケジュールでの設定
1  ＞Lambda、Eventblidgeを手動でスケジュールの数×２（StartとStop）作成

※自動起動停止したいインスタンスの指定は、Lambdaに直接書くか、対象を一覧CSVにしてLambdaで指定する。

2＞CloudFormation テンプレートを使用してスケジュールの数だけスタックを作成（LambdaとEventblidgeはStartとStopが自動作成）

※インスタンスの指定は、スタックのパラメータ（Lambdaの環境変数）に登録するか、対象を一覧CSVにしてLambdaで指定する。

## コスト
 [Amazon EventBridge の料金](https://aws.amazon.com/jp/eventbridge/pricing/)
カスタムイベント	：百万件の公開済みカスタムイベントごとに 1.00USD
サードパーティー (SaaS) のイベント	：百万件の公開済みイベントごとに 1.00USD
イベントからもう一つのバスへ	：百万件の送信済みイベントごとに 1.00USD

[AWS Lambda 料金](https://aws.amazon.com/jp/lambda/pricing/)
リクエスト：リクエスト100万件あたり0.20USD
実行時間：GB-秒あたり0.0000166667USD

## 注意
* Amazon EventBridgeでのスケジュール設定は、UTCで行われるためマイナス9時間で設定する。

# Instance Schedulerで実装する

 **1. スタックの作成** 

* 「 [AWSコンソールで起動する](https://aws.amazon.com/jp/solutions/implementations/instance-scheduler/) 」＞サインイン（入り直す必要あり）、リージョンを東京へ変更
* 名前、Account Id、TimeZoneなどの [パラメータ](https://docs.aws.amazon.com/ja_jp/solutions/latest/instance-scheduler/deployment.html) を設定
* 「スタックの作成」

 **2. スケジュール定義、ピリオド定義** 

* 「DynamoDB」＜「テーブル」にて「InstanceScheduler-ConfigTable-*」クリック
* 「項目を表示」
* ピリオド定義 (type 属性が period) の office-hours のチェックボックスを選択し、「アクション」から「コピー」をクリック
* 下記設定し、「項目の作成」をクリック（例：月曜日の9:00~18:00のみ起動する設定）

| フィールド名| 値 |
|:-----------:|:------------:|
| name  | mon-start-9am  |
| begintime  | 9:00  |
| endtime  | 18:00  |
| description  | Office hours on Monday  |
| weekdays[0]  | mon  |	

* seattle-office-hours のチェックボックスを選択し、アクションから「コピー」をクリック
* 下記設定し、「項目の作成」をクリック

| フィールド名| 値 |
|:-----------:|:------------:|
| name  | mon-9am-18pm  |
| timezone  | Asia/Tokyo  |
| description  | Monday 9am to 18pm in Japan (JST)  |
| periods[0]  | mon-start-9am  |	

 **3. インスタンスへのタグ付け** 

* マネジメントコンソールのインスタンス一覧画面 から対象のインスタンスを選択し、タグのタブから「タグを管理」をクリック
* 「タグを追加」をクリックし、キーに CloudFormationのInstance Scheduler tag name で指定した値 (初期値は Schedule)、値に適用するスケジュール定義の name の値 (今回は mon-9am-18pm) を入力
* 「保存」

 **4. リモートスタックの作成** 

* 「 [リモートスタックの作成](https://docs.aws.amazon.com/ja_jp/solutions/latest/instance-scheduler/deployment.html#step5) 」＞サインイン（入り直す必要あり）、リージョンを東京へ変更
* InstanceSchedulerAccount、Namespace（１のNamespaceと同じになるように設定）などパラメータを設定
* 設定した時間になり、スケジュール通り起動・停止していることを確認


## 複数インスタンス・複数スケジュールでの設定
起動・停止したいスケジュールをDynamoDBのpiriodとscheduleで設定し、対象インスタンスにschedule名をタグ付けする。
問題なく起動・停止した場合はタグにScheduleMessageが作成され、実行時間が確認できる。
→スタック２つ、Lambda１つ、Eventblidge１つ、DynamoDBのScheduleとpiriodはスケジュールの数分作成すればよい。
　ただ、どの対象インスタンスがどのスケジュールで設定されているか一覧で分からないため、別途管理する必要があるかも。

## コスト
例：月額約 33.24 ドル　[Instance Schedulerの費用](https://docs.aws.amazon.com/ja_jp/solutions/latest/instance-scheduler/cost.html)
※5 つのアカウント * 2 リージョン * 3 サービス (Amazon EC2/Amazon RDS/Aurora クラスター) * 2 つのスケジュール数 * 1 日あたりのスケジューラーあたりのアクションの最小数 2 * 各 Runbook の 4 つのステップの場合
→コストは起動停止対象のインスタンス数は関係せず、実行数の合計によって決まる。

## 注意

* 「2. スケジュール定義、ピリオド定義」の際、 **config** と **schedule** のTimeZoneを「Asia/Tokyo」にする。
* 3までの手順で実行するとオートメーションのステップ１でエラーとなる。「4. リモートスタックの作成」で別途スタックを立てることで、１つめのスタックのAutomationExecutionRoleのロールが作成され、正常に動作する。