関連づけられてないEIPをSlack通知 & 削除するLambda

# はじめに
EIPの料金体系についてご存知でしょうか。
以下のケースで、EIPは料金が発生します。

1. 実行中のインスタンスと関連付けられている追加の IP アドレス(= インスタンスは実行中だが2個以上EIPが関連づけられている)
2. 実行中のインスタンスと関連付けられていないEIP
3. 関連付いているEC2が停止している
4. EIPを、1ヶ月に100回以上リマップ

https://aws.amazon.com/jp/ec2/pricing/on-demand/

ちょくちょく個人の検証環境にて2番目の**EC2に関連付いていない（= 使わずに放置されている）EIP**が湧くことがあります。（単純に消し忘れ）

EIPが1つ生き残っていた場合、1ヶ月あたり大体以下の無駄な費用が発生します。
（これを多いととるか少ないととるかはお任せします）

```
0.005 × 24 × 30 × 135 = 486円

※計算に必要な値
EIPの料金：$0.005/h
1ヶ月：30日とする
1ドル：135円とする
```

そこで今回紹介するLambdaを使ってこのEIPを一網打尽にしてやることにしました。
# 仕様
- 検証環境に存在する何も紐づけられていないEIPをSlack通知・削除(全リージョン)
- Amazon EventBridgeと組み合わせて毎日定時で実行
- 容赦ない削除、慈悲はない 
- 削除されたくない場合、以下で回避可能
    - EC2に紐づけておく
- 以下のようなSlack通知
![ken_png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2109911/49b02e49-9d12-570a-bc21-90720516d0aa.png)

# 作成方法
## 作成までの流れ
今回はpythonのファイルと外部モジュールをzipパッケージ化してLambdaへアップロードします。
手順は以下となります。
1. Cloud9でzipパッケージを作成
2. Lambdaを作成
3. Amazon EventBridgeのルール作成
## 1. Cloud9でzipパッケージを作成
1-1. Cloud9のコンソールで[Create environment]をクリック
1-2. 名前を適当につけて[Next Step]
1-3. 以降のステップは何も変更しないで[Next Step] > [Create environment]
1-4. Cloud9が起動したら、ターミナルで以下のコマンドを実行
```
# ディレクトリを作成し、作成したディレクトリへ移動
$ mkdir lambda_workspace && cd lambda_workspace

# Lambdaに呼び出されるPythonのファイルを作成
$ touch lambda_function.py
```
1-5. 「lambda_function.py」を開き、以下のスクリプトをコピペして保存
```
# coding:utf-8

import datetime
from email.headerregistry import Address
from http import client
import os
import json

import boto3
import pytz
import requests

SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
SLACK_CHANNEL = os.environ['SLACK_CHANNEL']

def get_eip():
    client = boto3.client('ec2')
    regions = client.describe_regions()['Regions']
    Eips = []
    for region in regions:
        client = boto3.client('ec2', region_name=region['RegionName'])
        response = client.describe_addresses()
        Addresses = response['Addresses']
        if not Addresses:
            continue
        for Address in Addresses:
            if 'AssociationId' not in Address:
                Eip = dict()
                Eip['id'] = Address['AllocationId']
                tags = Address.get('Tags')
                Eip['name'] = 'null'
                if tags is not None:
                    for tag in tags:
                        if tag['Key'] == 'Name':
                            Address['name'] = tag['Value']
                Eip['PublicIp'] = Address['PublicIp']
                Eip['region'] = region['RegionName']
                Eips.append(Eip)
    return Eips
def delete_eip(Eips):
    if not Eips:
        return None
    for Eip in Eips:
        client = boto3.client('ec2',region_name=Eip['region'])
        client.release_address(AllocationId=Eip['id'])
def build_message(Eips):
    if not Eips:
        return None
    now = datetime.datetime.now(pytz.timezone('Asia/Tokyo'))
    text = '{} アタッチされていないEIP一覧'.format(now.strftime('%Y年%m月%d日%H時%M分'))
    atachements_text = ''
    for Eip in Eips:
            atachements_text += '削除→ name: {}, id: {}, ip: {}, region: {}\n'.format(
                Eip['name'],  Eip['id'], Eip['PublicIp'], Eip['region'])
    atachements = {'text': atachements_text, 'color': 'red'}
    message = {
        'text': text,
        'channel': SLACK_CHANNEL,
        'attachments': [atachements],
    }
    return message
def post_message(message):
    response = requests.post(SLACK_WEBHOOK_URL, data=json.dumps(message))
    response.raise_for_status()
def lambda_handler(event, context):
        eips = get_eip()
        message = build_message(eips)
        if message:
            post_message(message)
            delete_eip(eips)
```
1-6. Cloud9のターミナルで以下のコマンドを実行
```
# 必要なライブラリ(pytzとrequests)を「lambda_workspace」のディレクトリへインストール
$ pip install pytz -t .
$ pip install requests -t .

# 「lambda_workspace」のディレクトリの中身をzip圧縮
$zip -r lambda_workspace.zip *
```
1-7. 作成されたzipファイルをダウンロード
![test_-_AWS_Cloud9-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2109911/fea7181c-077f-f790-b925-3c6fc360d463.png)

## 2. Lambdaを作成
2-1. Lambdaのコンソールで[関数の作成]をクリック
2-2. 任意の関数名を入力し、ランタイムに「Python3.9」を選択し、[関数の作成]をクリック
2-3. 作成されたLambdaの[コード]タブから[アップロード元]　> [.zipファイル]をクリック
![releaseEIP-test_-_Lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2109911/b2bf558a-140a-40b9-360d-9d688a6ac39f.png)
2-4. 1の手順で作成したzipファイルをアップロード
2-5. [設定]タブ > [アクセス権限] > ロールをクリック
![releaseEIP-test_-_Lambda-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2109911/f50b31ab-f22e-1ee5-3894-ec85b1fef681.png)
2-6. Lambdaのデフォルトロールにアタッチされているポリシーをクリック
2-7. [ポリシーの編集]をクリックし、ポリシーに以下の権限を追加
- ec2:DescribeRegions
- ec2:DescribeAddresses
- ec2:ReleaseAddress

2-8. 作成したLambdaの[設定]タブから[環境変数] > [編集]をクリックし、以下を入力
（※値は適宜修正してください）

| キー | 値 | 
| --- | --- |
| SLACK_CHANNEL | 通知したいチャンネル名| 
| SLACK_WEBHOOK_URL | SlackのWebhook URL (例：https://hooks.slack.com/services/~)|

2-9. 作成したLambdaの[設定]タブから[一般設定]でタイムアウトを３分に変更
## 3. Amazon EventBridgeの設定
※スケジュールで(毎日)Lambda実行したい場合はこの手順が必要
3-1. Amazon EventBridgeのコンソールで[ルール]をクリック
3-2. [名前]を適当に入れて[ルールタイプ]スケジュールを選択し[次へ]
3-3. Cron式でスケジュールを入力
以下の例は毎日18:00に実行される
![Event.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2109911/1007f7b0-ff8b-08d5-99d7-1efb41889cde.png)
3-4. ターゲットで前の手順で作成したLambdaを選択し[次へ]
3-5. あとは任意で設定し、ルールを作成

これで毎日18:00に何も関連付けられていないEIPを駆逐できます！！！
# おわりに
Pythonとかのスクリプトを書くときはいつもハンズオンとか勉強のためでしたが、今回初めてやりたいことベースでコードを書きました。
完成した時は達成感が半端なかったです！！
ここから、このLambdaをAWS CDKで作成したり、Cloud9でzipを作成した部分をもっとスマートにできるかな〜と現在、夢が膨らんでおります！