AWSのEBSボリュームにタグをつけるLambdaを作った記録
EBSボリュームは管理しないと無駄にお金がかかります。
何気なくEC2を作るとEBSボリュームが作られます(既存のボリュームを使った場合を除く)。
EC2を削除(終了)してもEBSボリュームはデフォルトで削除されません。
結果、気が付いたら使ってないEBSボリュームが残っていることがあります。
aws.amazon.com
EC2インスタンスを削除するときはEBSボリュームも削除します。
それでもアタッチされていないEBSボリュームが残ることはあります。
複数人で長期間使っていればうっかりアタッチされていないEBSボリュームが残ることはあります。
とはいえ、本当にアタッチされていなければ削除していいのか?誰かが何かの目的で残しているのかも?
となった時に何に使われていたがわかる情報があると助かります。
やりたいこと
前提 : EC2にはNameタグをつけておきます。
EC2インスタンスには、Nameというタグをつけてインスタンスを使っているプロジェクトやサーバの情報をValueに入れておきます。
例えば、ponsukeプロジェクトのMySQLデータベースサーバにしているインスタンスなら「ponsuke-MySQL」をNameタグのValueに入れておく、みたいな。
Lambdaを作る記録
Lambdaの実行権限を作成します。
IAMのポリシーを作成します。
- AWSマネジメントコンソールにある[IAM] > サイドメニューの[ポリシー] > [ポリシーの作成]ボタンで作成画面を開きます。
- [JSON]タブを開いて下にある内容を入力します。
- [ポリシーの確認]ボタンで確認画面へ遷移して[名前(必須)]と[説明(任意)]を入力します。
- [ポリシーの作成]ボタンでポリシーを作成して一覧画面に戻ります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:CreateTags", "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" } ] }
IAMのロールを作成します。
Lambda関数を作成します。
- AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
- 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
- オプション : [一から作成]
- 関数名 : attatch_name_tag_for_ebs_volume(任意の関数名でOK)
- ランタイム : Python3.8
- 実行ロール : 既存のロールを使用する
- 既存のロール : 作成したロールを選択します。
Lambda関数を実行するトリガーを作成します。
- [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
- プルダウンから[CloudWatch Events]を選択します。
- 各入力欄を記載します
- ルール : [新規ルールの作成]
- ルール名(必須) : attatch_name_tag_for_ebs_volume(任意の関数名でOK)
- ルールタイプ : [スケジュール式]
- スケジュール式の書き方を参考にLambda関数を実行する予定を入力します。
- [追加]ボタンでトリガーを作成します。
関数の内容を実装します。
- EBSボリュームを取得するdescribe_volumes()の使い方
- EC2インスタンスを取得するdescribe_instances()の使い方
- EBSボリュームにタグをつけるcreate_tags()の使い方
import boto3 ec2 = boto3.client('ec2') print('Loading function') def get_name_tag_value(tags): """ タグリストからNameタグの値を取得する. Parameters ---------- tags 辞書形式のタグリスト """ name_tag_value = '' for tag in tags: if tag['Key'] == 'Name': name_tag_value = tag['Value'] break return name_tag_value def get_volumes_no_name_tag_and_attached(): """ [アタッチされていて][Nameタグが設定されていない]EBSボリュームを取得する. """ volumes = ec2.describe_volumes( Filters=[ { 'Name': 'attachment.status', 'Values': ['attached'] } ] )['Volumes'] volumes_no_name_tag = [] for volume in volumes: if 'Tags' not in volume: # タグが設定されていない場合:Nameタグのないボリュームとする volumes_no_name_tag.append(volume) else: # タグが設定されている場合 name_tag_value = get_name_tag_value(volume['Tags']) if name_tag_value == '': # Nameタグの値を取得できない場合:Nameタグのないボリュームとする volumes_no_name_tag.append(volume) return volumes_no_name_tag def get_ec2_instance_name_tag_value(volume): """ EBSボリュームにアタッチされているEC2インスタンスに設定されているNameタグの値を取得する. Parameters ---------- volume 対象となるEBSボリューム """ # アタッチされているEC2インスタンスのIDを取得する instance_id = volume['Attachments'][0]['InstanceId'] # EC2インスタンスのIDからインスタンスに設定されているタグ群を取得する tags = ec2.describe_instances( Filters=[ { 'Name': 'instance-id', 'Values': [instance_id] } ] )['Reservations'][0]['Instances'][0]['Tags'] # EC2インスタンスについているNameタグの値を取得する name_tag_value = get_name_tag_value(tags) return name_tag_value def set_name_tag_for_volume(volume_id, name_tag_value): """ EBSボリュームにNameタグを設定する. Parameters ---------- volume_id NameタグをつけるEBSボリュームID name_tag_value EC2インスタンスについているNameタグの値 """ response = ec2.create_tags( Resources=[volume_id], Tags=[{'Key': 'Name', 'Value': name_tag_value}] ) return 0 def lambda_handler(event, context): volumes = get_volumes_no_name_tag_and_attached() for volume in volumes: # EBSボリュームのIDを取得する. volume_id = volume['VolumeId'] print(str(volume_id) + 'にはNameタグが付いていません') name_tag_value = get_ec2_instance_name_tag_value(volume) # EC2インスタンスにNameタグが設定されている場合に処理を実行する. if name_tag_value != '': set_name_tag_for_volume(volume_id, name_tag_value) print(str(volume_id) + 'のNameタグに「' + name_tag_value + '」とつけました') return 0
失敗したこと
describe_volumes()の実行権限がIAMロールになかった
[ERROR] ClientError: An error occurred (UnauthorizedOperation) when calling the DescribeVolumes operation: You are not authorized to perform this operation.
- 事象 : describe_volumes()を実行したらエラーになった
- 原因 : describe_volumes()を実行する権限がないから
- エラーの時のIAMロールの権限設定 > 「ec2:DescribeInstances」しかない
...省略... "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:CreateTags", "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], ...省略...
- 対応 : 「ec2:DescribeVolumes」を権限設定に追加する
...省略... "Effect": "Allow", "Action": [ "ec2:DescribeVolumes", <<< 追加 "ec2:DescribeInstances", "ec2:CreateTags", "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], ...省略...
describe_volumesの引数の型が不正だった
[ERROR] ParamValidationError: Parameter validation failed: Invalid type for parameter Filters[0].Values, value: Name, type: <class 'str'>, valid types: <class 'list'>, <class 'tuple'>
- 事象 : Filtersに指定したValuesに文字(str)を指定して実行したらエラーになった
# Nameタグが設定されていないEBSボリュームを取得する response = ec2.describe_volumes( Filters=[ { 'Name': 'tag:Name', 'Values': '' } ] )['Volumes']
- 原因 : Valuesにlistかtupleのシーケンス型を使っていないから
- [ドキュメントにもValues (list) と書いてあります。]
- 対応 : Valuesをlistで指定する
{ 'Name': 'tag:Name', 'Values': [''] <<<<<<<<<< 修正 }