S3の使用状況を拡張子別に取得してみた~Lambda編~

こんにちは。katoです。

今回はS3の使用状況を拡張子別に取得するLambda関数のご紹介を致します。

 

概要

今回はS3バケットにおける拡張子別の使用量を取得するLambda関数をご紹介致します。

分析ツールのように使用したり、煩雑になりがちなS3バケットを整理したりするのに役立つかと思います。

なお、今回ご紹介する拡張子別のS3使用状況の取得というのは、もともと、Step Functionsでパラレル処理を実行したいと思って、処理に時間の掛かりそうなタスクを考えていたら思いついたものとなります。

そのため、S3をがっつり利用しているような方だと、Lambdaがタイムアウトする可能性が御座いますのでご注意ください。

今回の内容をStep Functionsでパラレル化したものを次回以降に記載させていただきたいと思います。

 

手順

それでは実際に設定を行っていきましょう。

とはいっても今回は、Lambda関数を作成するのみとなります。

import json
import boto3
import re

s3 = boto3.client('s3')
sns = boto3.client('sns')

def lambda_handler(event, context):
    buckets = []
    listbucket = s3.list_buckets()
    for i in range(len(listbucket['Buckets'])):
        buckets.append(listbucket['Buckets'][i]['Name'])
    
    message = ""
    for j in range(len(buckets)):
        size = 0
        count = 0
        extentions = []
        counts = []
        sizes = []
        listobj = s3.list_objects_v2(
            Bucket=buckets[j]
        )
        if "Contents" in listobj:
            for num in range(len(listobj['Contents'])):
                key = listobj['Contents'][num]['Key']
                prefix = key.rsplit("/", 1)
                if len(prefix) != 1:
                    keys = prefix[1].rsplit(".", 1)
                    if len(keys) != 1:
                        extention = keys[1]
                    else:
                        extention = "other"
                else:
                    keys = prefix[0].rsplit(".", 1)
                    if len(keys) != 1:
                        extention = keys[1]
                    else:
                        extention ="other"
                if extention in extentions:
                    indexnum = extentions.index(extention)
                    sizes[indexnum] = sizes[indexnum] + listobj['Contents'][num]['Size']
                    counts[indexnum] += 1
                else:
                    extentions.append(extention)
                    sizes.append(listobj['Contents'][num]['Size'])
                    counts.append(1)
                size = size + listobj['Contents'][num]['Size']
                count += 1
            objlen = len(listobj['Contents'])
            
            while (objlen % 1000 == 0):
                startlen = objlen -1
                lastkey = listobj['Contents'][startlen]['Key']
                listobj = s3.list_objects_v2(
                    Bucket=buckets[j],
                    StartAfter=lastkey
                )
                for num in range(len(listobj['Contents'])):
                    key = listobj['Contents'][num]['Key']
                    prefix = key.rsplit("/", 1)
                    if len(prefix) != 1:
                        keys = prefix[1].rsplit(".", 1)
                        if len(keys) != 1:
                            extention = keys[1]
                        else:
                            extention = "other"
                    else:
                        keys = prefix[0].rsplit(".", 1)
                        if len(keys) != 1:
                            extention = keys[1]
                        else:
                            extention ="other"
                    if extention in extentions:
                        indexnum = extentions.index(extention)
                        sizes[indexnum] = sizes[indexnum] + listobj['Contents'][num]['Size']
                        counts[indexnum] += 1
                    else:
                        extentions.append(extention)
                        sizes.append(listobj['Contents'][num]['Size'])
                        counts.append(1)
                    size = size + listobj['Contents'][num]['Size']
                    count += 1
                objlen = len(listobj['Contents'])
            message = message + "\n\nBucketName: " + buckets[j]
            message = message + "\nTotalSize: " + str(size) + " byte"
            message = message + "\nObjectCount: " + str(count)
            rankcount = 0
            message = message + "\n[Details]"
            while (len(extentions) != 0 and rankcount < 5 ):
                maxindex = sizes.index(max(sizes))
                message = message + "\n" + extentions[maxindex] + " object : " + str(sizes[maxindex]) + " byte"
                message = message + "\n" + extentions[maxindex] + " object count: " + str(counts[maxindex])
                del extentions[maxindex]
                del sizes[maxindex]
                del counts[maxindex]
                rankcount += 1
        else:
            message = "\n\n" + buckets[j]
            message = message + "\n0"
    
    request = {
        'TopicArn': "arn:aws:sns:ap-northeast-1:123456789000:xxxxxxxxxx",
        'Message': message,
        'Subject': "[MESSAGE]S3 Usage"
    }
    sns.publish(**request)

 

コードの内容といたしましては、オブジェクトのキーとサイズを取ってごちゃごちゃしているだけとなりますが、簡単な説明と注意点を以下に記載させていただきます。

・list_objects  APIに関して
オブジェクトの一覧取ってきてfor文回せば行けるだろーって感じでLambda関数を作成しはじめましたが、どうもTotal Sizeがコンソールの表示と合わない。
おかしいなーと思ってboto3のページを見ていたら説明にしっかり書いてありました。

最大1000個。

見落としてました。
StartAfterのオプションで始点を指定してあげるようにしたら普通にうまくいきました。

少し処理は面倒になりますが、オブジェクト数が1000を超える場合には、どこからオブジェクトの取得を開始するのか指定してあげる必要があります。

今回はlist_objectsの返り値が1000個である限り、処理を繰り返すような条件にしております。

なお、今回はlist_objects_v2 APIを利用していますが、古いほうのlist_objects APIではStartAfterの指定ができなさそうでしたのでご注意ください。

・出力の制限に関して
今回のLambda関数では出力を、使用量が多い拡張子、5個に制限しております。

最初はすべての拡張子を出力するように設定していたのですが、思っていたよりも拡張子が多く、出力メッセージがかなり見づらくなってしまったので、このような制限を設けております。

情報自体はすべての拡張子で取得してありますので、最後の方に記載してあるwhile文の条件式を変えれば、データの利用は可能です。

DynamoDBやAmazon ES等を使うのであれば、このような制限は不要かと思われます。

・otherに関して
拡張子の無いオブジェクトになります。

・対象リージョン
全リージョンのバケットが対象となります。

 

動作確認

それでは実際にLambda関数を実行して動作を確認してみましょう。

実行が完了するのには結構時間がかかりますが、処理が完了するとSNS経由で以下のようなメールが届きます。

Lambda関数を実行して動作を確認

バイト表示なので少し見づらいですが、拡張子ごとにしっかりデータが取れました。

 

まとめ

今回は拡張子別のS3利用量を取得するLamdba関数をご紹介いたしました。

使用状況の分析やバケットの整理などにご利用いただけるかと思います。

Step Functionsを利用したパターンでも記事を作成する予定となっておりますので、宜しければそちらもご覧ください。

 

 

 

このブログの著者

 

 

AWS相談会