AWSのリソースって使っていない時は止めたいよね！

#AWSの料金が思ったより高かった
AWSに限らず、クラウドでは簡単にサーバを立てて使う事ができるので、ちょっとした実験やプログラムの勉強、PoC環境や開発環境を作るのに、とても便利です。
ですが、こういう用途の場合、予算があまり無い事が多いので、月末に請求書を見て「えっ、思っていたより高い」と思う事があります（ありました）。
そこで、AWSの料金を安くするために色々試してみました。
##AWS料金の大半はEC2
請求書の内訳をみると、80～90％はEC2でした。
なので、EC2の料金を抑えるのがAWS料金を抑えるのに最も効果的です。
EC2料金の内訳は以下の通りです。

- Amazon Elastic Compute Cloud NatGateway
    - NATに対する時間課金
- Amazon Elastic Compute Cloud running Linux/UNIX
    - EC2に対する時間課金
- EBS
    - EC2が使っているEBSに対するサイズ課金（EC2を止めていても掛かる）
- Elastic IP Addresses
    - インスタンスにアタッチされていないIPアドレスに対する時間課金
- Elastic Load Balancing - Application
    - ロードバランサーに対する時間課金

###EBS以外は時間課金
EC2の料金はEBS以外は時間課金です。使わなければ料金は掛かりません。
しかし、EBSのサイズ課金はEC2を止めていても掛かるので、無駄にデカいサイズのSSDを使っていると、無駄に課金されます。
\:+1: EC2作成時のディスサイズは必要最低限にして、足りなくなったら追加しましょう。
\:+1: AMIで保存されるイメージのサイズも課金対象なので、不要なAMIは削除しましょう。
#使っていない時間は止めれば良いよね
時間課金を抑えるには、インスタンスの稼働時間を減らせば良いのですが、使う時間を減らすのでは本末転倒なので、使っていない時間帯（夜中、人によっては朝）にインスタンスを止めたいと思います。
##Lambda関数でインスタンスを止める
使わない時に止めたいのはEC2インスタンスですが、NATゲートウェイを使っている場合、この課金も安くないので、止めたいところです。残念ながら、2018年11月現在、NATゲートウェイはEC2インスタンスのように停止する事が出来ず、削除することしかできません。
NATゲートウェイを削除した場合、アタッチされているElastic IP（以下EIP)が不使用状態になって、（NATゲートウェイを止めている間）EIPに時間課金が掛かります。
EIPはネットワークインターフェースにアッタッチされているので、NATゲートウェイを削除しても直ぐにリリースする事が出来ません。なので、EC2やNATを停止、削除するLambda関数と、使っていないEIPを削除するLambda関数を2つ作る必要があります。
###Lambda関数用のIAMロール作成
Lambda関数に、EC2インスタンスの起動・停止、NATゲートウェイの作成・削除、EIPの作成・解放権限を持たせるために、以下のロールを付与したIAMロールを作成します。

- AmazonEC2FullAccess
- AmazonVPCFullAccess
- AWSLambdaBasicExecutionRole 

###インスタンス停止用Lambda
上記で作成したロールを使って、停止用のLambdaを作成します。

|![lambda.PNG](https://qiita-image-store.s3.amazonaws.com/0/309509/f4c6dc8a-0e5c-4caa-d5c6-f44ecf0d46de.png)
|:-:|
インスタンスを停止するスクリプトはこんな感じです。
記述できる言語は、C#、Go、Java、Node.js、Pythonです。
ここではPythonでサンプルを書いてみました。

```python:python3.6
import os
import boto3

subnet = os.environ['SUBNET_ID']
rtb = os.environ['RTB_ID']
region = os.environ['REGION']
ec2ids = os.environ['EC2']
client = boto3.client('ec2')

def stop_ec2():
    instances = [id.strip() for id in ec2ids.split(',')]
    ec2 = boto3.client('ec2', region_name=region)
    ec2.stop_instances(InstanceIds=instances)

def stop_natgw():
    filters = [ { 'Name': 'subnet-id', 'Values': [subnet] },
                { 'Name': 'state', 'Values': ['available'] } ]
    response = client.describe_nat_gateways(Filters=filters)
    natgw = response['NatGateways'][0]['NatGatewayId']
    client.delete_nat_gateway(NatGatewayId=natgw)

def detach_natgw():
    response = client.delete_route(
        DestinationCidrBlock = '0.0.0.0/0',
        RouteTableId = rtb
    )

def lambda_handler(event, context):
    stop_ec2()
    detach_natgw()
    stop_natgw()
```
この関数はリージョンID、サブネットID、ルータID、EC2インスタンスIDを環境変数で定義しています。
環境変数には文字列しか記述できませんので、停止したいEC2インスタンスが複数ある場合は、カンマ(,)で区切って複数記述してください。

|![env.PNG](https://qiita-image-store.s3.amazonaws.com/0/309509/2ac4885a-a86c-9265-d25d-5a41046d025c.png)
|:-:|
###不使用EIPリリース用Lambda
ネットワークインターフェースにアタッチされていないEIPをリリースするLambdaはこんな感じです。

```python:python3.6
import boto3

client = boto3.client('ec2')

def delete_ip():
    response = client.describe_addresses(
        Filters=[
            {'Name': 'domain','Values': ['vpc'] }
        ]
    )
    for rec in response['Addresses']:
        if "NetworkInterfaceId" not in rec:
            client.release_address(AllocationId=rec['AllocationId'])

def lambda_handler(event, context):
    delete_ip()
```
#使う時には動いていて欲しいよね
という事で、止めたままだと寂しいので、自動起動用のLambdaも作ってみましょう。
###インスタンス起動用Lambda
NATゲートウェイ作成時に自動的にEIPを作成してアタッチします。
環境変数の指定は停止用Lambdaと同じです。
スクリプトはこんな感じです。

```python:python3.6
import os
import boto3

subnet = os.environ['SUBNET_ID']
rtb = os.environ['RTB_ID']
region = os.environ['REGION']
ec2ids = os.environ['EC2']
client = boto3.client('ec2')

def start_ec2():
    instances = [id.strip() for id in ec2ids.split(',')]
    ec2 = boto3.client('ec2', region_name=region)
    ec2.start_instances(InstanceIds=instances)

def start_natgw():
    # EIPを新規作成
    result = client.allocate_address(
        Domain='vpc'
    )
    eip = result['AllocationId']
    # IPを固定にしたい場合は、使いたいEIPのアロケーションIDを環境変数にセットして以下を使う
    # eip = os.environ['EIP']
    response = client.create_nat_gateway(
        AllocationId=eip,
        SubnetId=subnet
    )
    natid = response['NatGateway']['NatGatewayId']
    client.get_waiter('nat_gateway_available').wait(NatGatewayIds=[natid])
    return(natid)

def atatch_natgw(natgw):
    response = client.create_route(
        DestinationCidrBlock = '0.0.0.0/0',
        NatGatewayId = natgw,
        RouteTableId = rtb
    )

def lambda_handler(event, context):
    start_ec2()
    natgw = start_natgw()
    atatch_natgw(natgw)
```

ここで注意する事は、EIPを新規に作成する毎にIPアドレスが変わる事です。
NATゲートウェイのIPを固定にしたい場合は、creata_nat_gateway()のAllocationIDパラメータにアッタッチしたいEIPのアロケーションIDをセットします（その場合、NATゲートウェイ停止中もEIPを保持し続けるので、EIPに時間課金が発生します）。
##CloudWatchで自動起動・停止
Lambdaを手動で動かして、動くことが確認できれば、自動起動・停止にしてしまいましょう。
CloudWatchのルール設定を使って、cron形式の指定でLambda関数を実行することができます。

|![rule.PNG](https://qiita-image-store.s3.amazonaws.com/0/309509/dc36614d-ee2e-c674-7530-519c7be7c55f.png)
|:-:|

時間に指定はUTCなので注意が必要です。
例えば、平日の午前8時に起動したい時は以下のように指定します。
```0 23 ? * SUN-THU *```
或いは、平日の午後8時に起動したい時は以下のように指定します。
```0 11 ? * MON-FRI *```

#さいごに
AWSのサービスを操作するためのPythonのライブラリ群は、こちらの[ドキュメント](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/index.html)を参考にしました。今回は[EC2のClient](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#client)だけで作ってみましたが、色々なサービスを操作出来るようなので、また試してみたいと思っています。

さて、ここで一つ注意点（落とし穴）があります。
EC2インスタンスの時間課金は秒単位ですが、NATゲートウェイやEIPは時間単位のようです。
上記のLambdaをテストする際、起動・停止する度に、（数秒しか使っていなくても）NATゲートウェイやEIPが1時間分づつ課金されてしまいます。
20回くらいテストしたので20時間分課金されてました。。。 \:scream:
課金を安くしようとしたのに、余計な課金が発生するという罠が！

それはともかく、新年早々、余計な課金に驚かなくて済むように、年末年始は使っていないリソースを止めましょう！（なお、本記事は[SRA Advent Calendar 2018](https://qiita.com/advent-calendar/2018/sra)に掲載のものです）