AWS環境におけるNameタグ自動付与機能の実装

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タグ付与の例です。

メール通知

実装手順

  1. AWS Lambda関数の作成
  2. ソースコードの記述
  3. Amazon CloudWatchEventsでの定期実行設定
  4. Amazon SNSトピックの作成・連携

AWS Lambda関数の作成 

Lambda関数の作成手順は下記を参考に行いました。

5分でわかる!AWS Lambda の使い方
AWS 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トピックの作成・連携

SNSトピック・サブスクリプションの作成

① Amazon Simple Notification Service (Amazon SNS)のコンソールを開く

左サイドメニューから「トピック」を選択⇒「トピックの作成」

手順①

② タイプ:「スタンダード」を選択

名前・表示名に任意の値を入力 ⇒ 画面右下の「トピック作成」を押下

手順②

③ SNSトピックが作成されたことを確認

手順③

④ 左サイドメニューから「サブスクリプション」を選択⇒「サブスクリプションの作成」を押下

手順④

⑤ トピックARN:前項①、②で作成したSNSトピックのARNを選択

⇒ プロトコル:「Eメール」を選択 ⇒ エンドポイント」通知先メールアドレスを入力

手順⑤

⑥ サブスクリプションが作成されたことを確認

ステータス:「保留中の確認」であることを確認

手順⑥

⑦ 通知先メール宛に下記のメールが届く

「Confirm subscription」を押下

手順⑦

⑧ 下記画面が表示される

手順⑧⑨  SNSコンソールのサブスクリプション画面に戻り、ステータス:「確認済み」となっていることを確認

手順⑨

実行失敗時の通知設定

① 対象のLambda関数を開き、「送信先の追加」を押下

手順①

② ソース:「非同期呼び出し」 条件:「失敗時」 送信先タイプ:「SNSトピック」

送信先:前項「SNSトピック・サブスクリプションの作成」にて作成したSNSトピックを選択 ⇒ 画面右下の「保存」を押下

手順②

③ SNSトピックの設定が完了したことを確認

手順③

最後に

今回、EC2を含む9つのリソースを対象にNameタグ自動付与機能を実装しました。
本稿を書く中で、この機能はNameタグだけでなく、他にも様々なタグに展開可能だと改めて実感しました。
環境によって要件は様々かと思いますが、少しでも参考にしていただければ嬉しいです。

 

タイトルとURLをコピーしました