特定タグが付いたInstanceを自動起動/停止するbot君をAWS Lambdaで作る

# 魔法の言葉、経費削減
- 開発環境にEC2使ってると、帰宅時のInstanceのShutdown忘れ起こりがち。
- 「塵も積もれば山となり」という言葉があるそうです。
- 多分どこも同じような事をやってる、EC2 Instanceの自動停止bot君をLambdaで作る。
(残業多めの会社だと存在を誰もが忘れた頃に悲劇のShutdownを容赦なく放ち、比較的存在嫌われがち)
- LambdaにScheduleEvent付いたし、毎日cron実行するだけのInstanceを殺すチャンス。

# 先人様の参考など
- RoleとかGUIの設定周りはこの辺で掴んでもらえると。  
[クラスメソッドさん]  
[LambdaのScheduleイベントでEC2を自動起動＆自動停止してみた](http://dev.classmethod.jp/cloud/aws/lambda-scheduled-event-updown/ "LambdaのScheduleイベントでEC2を自動起動＆自動停止してみた")  
[EC2やRDS、ELBの停止忘れ、削除し忘れを防ぐ！LambdaScheduleイベント]
(http://dev.classmethod.jp/cloud/aws/lambda-scheduled-event-info/ "EC2やRDS、ELBの停止忘れ、削除し忘れを防ぐ！LambdaScheduleイベント ")

# 今回やりたいこと
- 今までは始業時間に自動起動、夜少し遅めの時間に自動停止するcron実行専用EC2が居た。
- 自動起動する際は、ちゃんと土日祝日を見極めて、平日のみ自動起動させる。
- Lambdaのpythonだと標準でboto3が使えるので、Filterとかで特定タグのInstanceに対象を絞る。


## 平日 or 土日 or 祝日
- 平日 or 土日の判定には、LambdaのScheduleEvent(cron)でMON-FRIを指定すれば平日のみ実行できるのが、後述のUTC問題があるため必要な場合はpython標準の date.weekday() などを使う
- 祝日の判定には色々あるけど結局Googleカレンダー様の日本の祝日に頼る。  
で、Google謹製のpythonモジュールがあるので使わせてもらう。  
[google-api-python-client]
(https://github.com/google/google-api-python-client "google-api-python-client")

## Lambdaで外部モジュール使う時はzipにしてコード全体をアップロードする
- Lambdaにはコードを書く方法が二通りあって、ブラウザ上でインライン編集しながら書く方法と、外部モジュールを使いたければモジュールごとzipにしてコードをアップロードする方法がある。
- google-api-python-client なんて当然使えるわけがないので、pipで任意ディレクトリにローカルインストールする。

```bash
cd [適当なディレクトリ]
sudo pip install --upgrade google-api-python-client -t ./

# 同じディレクトリ内でLambdaで実行するメインファイルを書く
vim hoge.py
```
```py:hoge.py
import boto3
import datetime
import sys

from apiclient.discovery import build


# https://console.developers.google.com/project ここらへんから
API_KEY = '[Googleの開発APIキー]'
CALENDAR_ID = 'ja.japanese#holiday@group.v.calendar.google.com'

# 年末年始とか会社の休みは自動起動しない(YYYY-MM-DDで列挙)。
company_holiday_list = []


# 関数名(ここではlambda_handler)と、ファイル名(ここではhoge.py)を
# LambdaのHandler名に設定する ex.) hoge.lambda_handler
def lambda_handler(event, context):
    client = boto3.client('ec2')

    # タグ名「AutoShutdown」がAUTOだと自動起動/停止
    # タグ名「AutoShutdown」がONだと自動停止のみ行う
    query_start = [
        {'Name': 'tag:AutoShutdown', "Values": ['AUTO']},
        {'Name': 'instance-state-name', "Values": ['stopped']}
    ]
    query_stop = [
        {'Name': 'tag:AutoShutdown', "Values": ['ON', 'AUTO']},
        {'Name': 'instance-state-name', "Values": ['running']}
    ]

    service = build(serviceName='calendar', version='v3', developerKey=API_KEY)
    events = service.events().list(calendarId=CALENDAR_ID).execute()
    holiday_list = []

    for item in events['items']:
        holiday_list.append(item['start']['date'])

    holiday_list.extend(company_holiday_list)

    # 発火元event名の確認
    try:
        # 自動起動の場合は土日祝日を除く平日のみ動作させる
        if '[Event sourceのARN(自動起動用ScheduleEvent)をコピー]' in event['resources']:
            if not str(datetime.date.today()) in holiday_list:
                client.start_instances(InstanceIds=get_instanceid(query_start))

        elif '[Event sourceのARN(自動停止用ScheduleEvent)をコピー]' in event['resources']:
            client.stop_instances(InstanceIds=get_instanceid(query_stop))

        elif '[Event sourceのARN(自動停止X分前告知用ScheduleEvent)をコピー]' in event['resources'] \
                and (not str(datetime.date.today()) in holiday_list):
            # 5分とか10分前にSlack通知するような処理をここに書くとより親切(自動停止爆死予防)

    except Exception as e:
        # エラー処理。Slackにスタックトレース投げるとか。

    print("SUCCESS: task succeeded")
    return


def get_instanceid(query):
    client = boto3.client('ec2')
    response = client.describe_instances(Filters=query)

    ec2_count = len(response['Reservations'])
    ec2_list = []

    if not ec2_count == 0:
        for i in range(0, ec2_count):
            ec2_list.append(response['Reservations'][i]['Instances'][0]['InstanceId'])
        return ec2_list
    else:
        print("SUCCESS: specified hosts is None")
        sys.exit()
```
- Lambdaにコードをアップロード

```bash
zip -r ~/hoge.zip .
aws lambda update-function-code --function-name [Lambda作成時に設定したfunction名] --zip-file fileb://~/hoge.zip
```
## 一応の補足
- [Event sourceのARN] は各自環境に書き換えてくださいm(__)m  
以下の画像でいうARNの部分をマルっとコピペです。
<img width="1126" alt="hoge.png" src="https://qiita-image-store.s3.amazonaws.com/0/19942/53ff159f-6f11-a6af-9cdb-be56b7ccc61e.png">
- 自動起動/停止でいちいちLambdaのFunctionを複数に分けたくないので、  
発火元event(ScheduleEvent名)によってstartかstopなのかを分岐させる。
- LambdaのScheduleEventはあくまでもUTCなので設定と取り扱いには注意！  
たとえばJSTで朝8時に自動起動させたい！な場合、UTCだとJSTと日付が変わるため、自動起動しないとか。


※このエントリはpython初心者により書かれたもので、完全な動作を保証するものではありません事を何卒ご承知おきください。むしろいい加減なアレです。