はじめに

以前、「関連づけられてないEIPをSlack通知 & 削除するLambda」を作成しました。

今回は上記のLambdaを応用し、何も関連づけられていない(= 「使用可能」状態)のEBSボリュームを削除するLambdaを作成してみました。

なぜ、作成するに至ったかというと、自分の所属するチームの検証環境には59台の「使用可能」状態のEBSが存在しており、毎月3000円ほど無駄な費用がかかっていたためです。

また、「使用可能」状態のEBSは「終了時に削除」が有効化されていないAMIからインスタンスを作成してしまうと、作成者の意図しないところで湧き出すのです。
なので、定期的に駆除したいと感じていました。

仕様

  • 検証環境に存在する何も紐づけられていないEBSをSlack通知・削除(全リージョン)
  • Amazon EventBridgeと組み合わせて毎日定時で実行
  • 容赦ない削除、慈悲はない
  • 削除されたくない場合、以下で回避可能
    • EC2に紐づけておく
    • スナップショットを取得しておく
  • 以下のようなSlack通知

「何も関連づけられていないEBSボリュームはお金かかるし必要ないよね!?」
「消されるのが嫌だったらスナップショットを取得しておいてね!」

という気持ちで作っております。

作成方法

Lambdaの中身であるpythonスクリプトや権限以外はEIP削除用Lambdaと同じです

作成までの流れ

今回は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 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_instances():
    client = boto3.client('ec2')
    regions = client.describe_regions()['Regions']
    instances = []
    for region in regions:
        client = boto3.client('ec2', region_name=region['RegionName'])
        response = client.describe_volumes(
            Filters=[
                {
                    'Name': 'status',
                    'Values': [
                        'available',
                    ]
                },
            ]
        )
        volumes = response['Volumes']
        if not volumes:
            continue
        for volume in volumes:
            instance = dict()
            instance['id'] = volume['VolumeId']
            tags = volume.get('Tags')
            instance['name'] = 'null'
            instance['owner'] = 'null'
            if tags is not None:
                for tag in tags:
                    if tag['Key'] == 'Name':
                        instance['name'] = tag['Value']
                    elif tag['Key'] == 'Owner':
                        instance['owner'] = tag['Value']
                    elif tag['Key'] == 'owner':
                        instance['owner'] = tag['Value']
            instance['type'] = volume['VolumeType']
            instance['size'] = volume['Size']
            instance['region'] = region['RegionName']
            instances.append(instance)
    return instances
def delete_volume(instances):
    if not instances:
        return None
    for instance in instances:
        client = boto3.client('ec2',region_name=instance['region'])
        client.delete_volume(VolumeId=instance['id'])
def build_message(instances):
    if not instances:
        return None
    now = datetime.datetime.now(pytz.timezone('Asia/Tokyo'))
    text = '{} アタッチされていないEBS一覧'.format(now.strftime('%Y年%m月%d日%H時%M分'))
    atachements_text = ''
    for instance in instances:
            atachements_text += '削除→ name: {}, type: {}, size: {}GB, id: {}, region: {}\n'.format(
                instance['name'], instance['type'], instance['size'], instance['id'], instance['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):
        instances = get_instances()
        message = build_message(instances)
        if message:
            post_message(message)
            delete_volume(instances)

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ファイルをダウンロード

2. Lambdaを作成

2-1. Lambdaのコンソールで[関数の作成]をクリック
2-2. 任意の関数名を入力し、ランタイムに「Python3.9」を選択し、[関数の作成]をクリック
2-3. 作成されたLambdaの[コード]タブから[アップロード元] > [.zipファイル]をクリック

2-4. 1の手順で作成したzipファイルをアップロード
2-5. [設定]タブ > [アクセス権限] > ロールをクリック

2-6. Lambdaのデフォルトロールにアタッチされているポリシーをクリック
2-7. [ポリシーの編集]をクリックし、ポリシーに以下の権限を追加

  • ec2:DescribeRegions
  • ec2:DescribeVolumes
  • ec2:DeleteVolume

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

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

2-9. 作成したLambdaの[設定]タブから[一般設定]でタイムアウトを3分に変更

3. Amazon EventBridgeの設定

※スケジュールで(毎日)Lambda実行したい場合はこの手順が必要
3-1. Amazon EventBridgeのコンソールで[ルール]をクリック
3-2. [名前]を適当に入れて[ルールタイプ]スケジュールを選択し[次へ]
3-3. Cron式でスケジュールを入力
以下の例は毎日18:00に実行される

3-4. ターゲットで前の手順で作成したLambdaを選択し[次へ]
3-5. あとは任意で設定し、ルールを作成

これで毎日18:00に何も関連付けられていないEBSを駆逐です!

おわりに

不要なEBSにお困りの皆様、是非こちらのLambdaをお使いください!!

元記事はこちら

関連づけられてないEBSをSlack通知 & 削除するLambda
著者:@suzuki_kento


アイレットなら、AWS で稼働するサーバーを対象とした監視・運用・保守における煩わしい作業をすべて一括して対応し、経験豊富なプロフェッショナルが最適なシステム環境を実現いたします。AWS プレミアコンサルティングパートナーであるアイレットに、ぜひお任せください。

AWS 運用・保守サービスページ

その他のサービスについてのお問合せ、お見積り依頼は下記フォームよりお気軽にご相談ください。
https://cloudpack.jp/contact/form/