SCSKの廣瀬です。
アマゾン ウェブ サービス(AWS)では、リソースを判別したり整理する目的で「タグ機能」が利用されています。中でもNameタグは、リソースの判別に重要な役割を担っています。
そこで今回は、Nameタグを自動で付与する仕組みをご紹介します。
Nameタグだけでなく、他のタグを実装したい場合にも参考になる内容があるかと思います。
是非参考にしていただければ嬉しいです。
機能の概要
本機能では、リソース作成者のユーザ名を値としたNameタグを自動で付与します。
今回は、Amazon EC2のインスタンスを対象としたNameタグ付与を例に記述します。
上図の詳細は以下の通りです。
① Amazon EC2インスタンス一覧からNameタグの無いリソースの情報を取得
② AWS CloudTrailにNameタグの無いリソース(=Nameタグ付与リソース)の情報を送信
③ CloudTrailログからNameタグ付与リソースの作成ユーザ名を取得
④「Name:○○(作成ユーザ名)」の形式でNameタグを付与
⑤ Nameタグ付与情報をSNSに送信
⑥ Nameタグ付与情報を利用者にメール通知
※Nameタグ付与リソースが存在しない場合、メール通知は行わない。
※下図はRouteTableリソースへのNameタグ付与の例です。
実装手順
- AWS Lambda関数の作成
- ソースコードの記述
- Amazon CloudWatchEventsでの定期実行設定
- Amazon SNSトピックの作成・連携
AWS Lambda関数の作成
Lambda関数の作成手順は下記を参考に行いました。
参照:「関数の作成」項目
ソースコードの記述
ソースコードはPythonのBuilderパターンを採用しています。
コードは以下の通りです。
import boto3 import json import abc class Guide(): def construct(self, builder, region) -> Tuple[str, int]: ec2_client = boto3.client('ec2', region) resources_list = builder.get_resources_list(ec2_client) resource_type = builder.get_resource_type() #Nameタグを付与するリソースが1つ以上存在する場合 if resources_list: for resource in resources_list: user_name = builder.get_user_name(resource, region) response = resource_type + ':' + '《' response += builder.put_nametag(ec2_client, resource, user_name) response += ' -> Name:' + user_name + '》' return response, len(resources_list) #Nameタグを付与するリソースが存在しない場合 else: return resource_type + ':---', 0 def send_sns(self, msg, entire_change_existence) -> None: sns_client = boto3.client('sns', 'eu-west-1') TOPIC_ARN = 'SNSトピックのARN' ###参照①### subject = 'Nameタグを' + str(entire_change_existence) + '個付与しました' sns_client.publish( TopicArn = TOPIC_ARN, Message = msg, Subject = subject ) class Resource(metaclass=abc.ABCMeta): @abc.abstractmethod def get_resources_list(self, ec2_client) -> list: pass @abc.abstractmethod def get_user_name(self, resource, region) -> str: pass @abc.abstractmethod def put_nametag(self, ec2_client, resource, user_name) -> str: pass @abc.abstractmethod def get_resource_type(self) -> str: pass class Ec2Builder(Resource): #Nameタグの無いリソースの探索 def get_resources_list(self, ec2_client) -> list: try: #EC2インスタンス一覧を取得 response = ec2_client.describe_instances() ###参照②### resources_list = [] for resource in response['Reservations']: name_tag_existence = False #タグがある場合 if 'Tags' in resource['Instances'][0]: for tag in resource['Instances'][0]['Tags']: #Nameタグがある場合 if 'Name' in tag.values(): name_tag_existence = True #Nameタグが無い場合 if not name_tag_existence: resources_list.append(resource['Instances'][0]['InstanceId']) if resources_list: return resources_list else: return None except Exception: raise Exception #CloudWatchログからリソース作成ユーザ名を取得 def get_user_name(self, resource, region) -> str: try: user_name = 'Unassigned' trail_client = boto3.client('cloudtrail', region) responses = trail_client.lookup_events( LookupAttributes=[ { 'AttributeKey': 'ResourceName', 'AttributeValue': resource } ] ) for response in responses['Events']: if 'RunInstances' in response.values(): ###参照③### user_name = response['Username'] return user_name except Exception: return 'Unassigned' #Nameタグの付与 def put_nametag(self, ec2_client, resource, user_name) -> str: try: ec2_client.create_tags( Resources=[ resource ], Tags=[ { 'Key': 'Name', 'Value': user_name } ] ) return resource except Exception: raise Exception #表示するリソース名を取得 def get_resource_type(self) -> str: return 'EC2' def lambda_handler(event, context): region_list = ['リージョンID_1', 'リージョンID_2']###参照④### builders_list = [Ec2Builder()] #対象とするリソースごとにクラスを作成してリスト化 entire_change_existence = 0 result = '' for region in region_list: region_change_existence = 0 result += '===' + region + '===' + '\n' for builder in builders_list: ans, num = Guide().construct(builder, region) if num: region_change_existence += num result += ans + '\n' #1つのリージョンにNameタグ付与リソースがある場合 if region_change_existence: entire_change_existence += region_change_existence #1つのリージョンにNameタグ付与リソースが無い場合 else: result += '変更なし' + '\n' print(result) #全対象リージョンに1つ以上のNameタグ付与リソースがある場合、SNSトピックをpush if entire_change_existence: Guide().send_sns(result, entire_change_existence)
参照① 25行目:’SNSトピックのARN’
後述の「SNSトピックの作成・連携」で作成したSNSトピックのARNを記載
例)arn:aws:sns:eu-west-1:123456789999:SNS_test
参照② 56行目:describe_instances
EC2以外のリソース一覧を取得する場合には、下記サイトでAPIを検索することで使い方を確認できます。
『Boto3 Documentation』 https://boto3.amazonaws.com/v1/documentation/api/latest/index.html
■リソース一覧を取得するAPI
Amazon Elastic Block Store(EBS):describe_volumes
アクセスコントロールリスト(ACL):describe_network_acls
Elastic IPアドレス(EIP):describe_addresses
Elastic Network Interface Card(ENI):describe_network_interfaces
RouteTable:describe_route_tables
SecurityGroup:describe_security_groups
Subnet:describe_subnets
Amazon VPC:describe_vpcs
参照③ 90行目:’RunInstances’
EC2以外のリソース作成時ログを取得する場合は、下記のイベント名に置き換えます。
Amazon Elastic Block Store(EBS):CreateVolume
アクセスコントロールリスト(ACL):CreateNetworkAcl
Elastic IPアドレス(EIP):AllocateAddress
Elastic Network Interface Card(ENI):CreateNetworkInterface
RouteTable:CreateRouteTable
SecurityGroup:CreateSecurityGroup
Subnet:CreateSubnet
Amazon VPC:CreateVpc
参照④ 117行目:[‘リージョンID_1’, ‘リージョンID_2’]
対象リージョンIDのリストを作成
例) [‘us-west-2’, ‘ap-northeast-1’]
AWS CloudWatchEventsでの定期実行設定
下記をご参照ください。
『AWS Documentation』 https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/RunLambdaSchedule.html
参照:「ステップ2:ルールを作成する」項目
Amazon SNSトピックの作成・連携
① Amazon Simple Notification Service (Amazon SNS)のコンソールを開く
左サイドメニューから「トピック」を選択⇒「トピックの作成」
② タイプ:「スタンダード」を選択
名前・表示名に任意の値を入力 ⇒ 画面右下の「トピック作成」を押下
③ SNSトピックが作成されたことを確認
④ 左サイドメニューから「サブスクリプション」を選択⇒「サブスクリプションの作成」を押下
⑤ トピックARN:前項①、②で作成したSNSトピックのARNを選択
⇒ プロトコル:「Eメール」を選択 ⇒ エンドポイント」通知先メールアドレスを入力
⑥ サブスクリプションが作成されたことを確認
ステータス:「保留中の確認」であることを確認
⑦ 通知先メール宛に下記のメールが届く
「Confirm subscription」を押下
⑧ 下記画面が表示される
⑨ SNSコンソールのサブスクリプション画面に戻り、ステータス:「確認済み」となっていることを確認
① 対象のLambda関数を開き、「送信先の追加」を押下
② ソース:「非同期呼び出し」 条件:「失敗時」 送信先タイプ:「SNSトピック」
送信先:前項「SNSトピック・サブスクリプションの作成」にて作成したSNSトピックを選択 ⇒ 画面右下の「保存」を押下
③ SNSトピックの設定が完了したことを確認
最後に
今回、EC2を含む9つのリソースを対象にNameタグ自動付与機能を実装しました。
本稿を書く中で、この機能はNameタグだけでなく、他にも様々なタグに展開可能だと改めて実感しました。
環境によって要件は様々かと思いますが、少しでも参考にしていただければ嬉しいです。