実装の方法
LambdaのPython(boto3)を使います。
1日1回実行するトリガーとして、EventBridgeを使います。
実装できると何がうれしいのか
1点目
EC2のdescribe_instances APIは、取得結果が冗長なデータ構造となっていて、Javascriptなどで取り込んだ時にやや扱いにくいです。
特に、Reservations[*].Instances[*]
の階層の深さと、タグの構造が [{'Key': 'タグキー名' }, {'Value': 'タグの値}]
となっている点が冗長でしょうか。
階層を単純な1次元配列(Pythonのリスト)とし、欲しい情報だけに絞ることで、プログラムから扱いやすくなります。
2点目
AWSのAPI/CLIでは、”現時点の”EC2の一覧を取得できますが、”x月x日時点の”EC2の一覧を取得したいとなった場合に、急に困ることになります。
1日1回Lambdaを実行して蓄積しておくことで、簡単に調べられるようになります。
IAMロールの作成
IAMロールを作成します。
最低でも、以下の4つのポリシー(IAMポリシー or インラインポリシー)を含んだIAMロールを作成しましょう。
- EC2 の DescribeInstances
- S3 の PutObject
- S3 の GetObject
- S3 の ListBucket
S3バケットの作成
S3バケットを作成します。
特にデフォルト設定で構いません。
Lambdaの概要
- EC2の一覧取得のAPI(describe_instances)を実行してEC2の情報を取得します。
- 深いデータ構造となっているのをシンプルな構造(1次元配列のインスタンス一覧)にします。
- APIの結果から必要な属性情報だけを切り出します。今回は、インスタンスID、タグ、サブネットID、インスタンスタイプ、の4つが必要情報の例としています。
- タグのデータ構造を
[{"Key":"Name", "Value":"Server1"}, ..]
から{"Name":"Server1", ..}
とシンプルにします。 - S3へ日付つきファイル名(キー名)と日付無しファイル名(キー名)の2つを保管します。
- 日付つきは ec2lite_YYYYMMDD.json、日付無しは ec2lite.json の名前とします。
Lambdaの作成
Lambdaを作成します。
言語は Python で、3.8 にします。
EC2の数が多い場合は、デフォルト設定値の 3秒 では間に合わない可能性があります。その場合は 10秒 などに増やしましょう。
lambda_function.py を以下のコードに置き換えます。
'your_bucket_name'
は自身のバケット名にします。
import json
import boto3
from datetime import datetime, time, timedelta, timezone
ec2 = boto3.client('ec2')
s3 = boto3.resource('s3')
# ↓↓ 正しいバケット名に書き換えてください ↓↓
s3_bucket = 'your_bucket_name'
# APIのデータにはJsonに存在しない日付型を含むため文字列に変換する
def datetime_json_parse(dict):
return dict.isoformat() if isinstance(dict, (datetime, date)) else dict
def lambda_handler(event, context):
ec2_lite_json = []
# ファイル名(オブジェクトキー名)1
s3_obj_path1 = 'ec2lite.json'
# ファイル名(オブジェクトキー名)2 +9時間で日本時間に
s3_obj_path2 = datetime.now(timezone(timedelta(hours=9))).strftime('ec2lite_%Y%m%d.json')
try:
ret_instances = ec2.describe_instances()
except Exception:
return { 'result': 'ng' }
for rsv in ret_instances.get('Reservations'):
for inst in rsv['Instances']:
tags_new = {}
# シンプルなタグの構造に変換
# "or []" はタグのないEC2でNoneType Errorとなる対策
for tag in inst.get('Tags') or []:
tags_new[ tag['Key'] ] = tag['Value']
# 必要な情報に絞ったデータを生成
# SubnetIdのみTerminated中のEC2でKey Errorとなるためget()
ec2_lite_json.append(
{
'InstanceId' : inst['InstanceId'],
'InstanceType' : inst['InstanceType'],
'Tags' : tags_new,
'SubnetId' : inst.get('SubnetId')
}
)
# キー1はputして保管、キー2はキー1からコピーして保管
s3.Object(s3_bucket, s3_obj_path1).put(
Body = json.dumps(ec2_lite_json, ensure_ascii=False, default=datetime_json_parse)
)
s3.Object(s3_bucket, s3_obj_path2).copy_from(CopySource={'Bucket': s3_bucket, 'Key': s3_obj_path1})
return { 'result': 'ok' }
EventBridgeの作成
EventBridgeを作成します。
マネージメントコンソールで操作する場合、Lambdaのコンソール上のままでトリガーの追加ボタンを押してEventBridgeを選択しても新規のルール作成ができます。
cron式で rate(1 day)
とすると日次で実行ができます。
もっとうれしいこと
サーバー運用していると、EC2インスタンスを終了(削除)したのは何月何日かをさかのぼって知りたい場合がありますが、消えてしまったEC2に対してAWSでは簡単に調べることができません。
このLambdaの実装が前提として、もう1つ別のLambdaを作成し、そのLambdaで生成したJsonを見るだけで簡単にEC2削除日を調べることができる、という展開も可能になります。
(別の記事で記載予定です)