IT

【aws】S3 バケットの容量を取得したい!

S3 の利用状況を表示する必要があったので、備忘として残します。

AWS CLI

パターン1

このコマンドだと、S3 に存在するオブジェクトを取得できます。

aws s3 ls s3://{対象バケット} --recursive --human --sum

YYYY-MM-DD HH:MM:SS xxx Bytes {対象ファイル名}
YYYY-MM-DD HH:MM:SS xxx Bytes {対象ファイル名}

Total Objects: xxx
Total Size: xxx.x GiB

パターン 2

このコマンドだと、S3 に存在するオブジェクトのバイト数だけを取得できます。

aws s3 ls s3://{対象バケット} --summarize --recursive | tail -n 1 | cut -d':' -f2
→xxxxxxxxxx(バイト数)

ポイント

- aws s3 ls: AWS CLIのS3コマンドで、S3バケットの内容をリストアップします。

- s3://{対象バケット}: 対象となるS3バケットのURIを指定します。

--summarize: コマンドの最後に合計オブジェクト数とサイズの要約を出力します。

--recursive: バケット内のすべてのサブディレクトリを再帰的に探索します。

tail -n 1 :入力の最後の1行だけを取得します。この場合、要約情報の最後の行(総サイズ)を取得します。

cut -d':' -f2 :再度パイプを使用して、前のコマンドの出力を次のコマンドに渡します。

 cut: テキストを特定の区切り文字で分割し、特定のフィールドを抽出するコマンドです。

 -d':': コロン(:)を区切り文字として使用することを指定します。

 -f2: 2番目のフィールド(コロンの後ろの部分)を抽出します。


パターン 2 の python コード

Lambda で動かす前提でデフォルトのライブラリだけで記述しています。

import subprocess
import csv
from datetime import datetime, timedelta
import os

BUCKET = "bucket-name"
OBJECT_PATH = "object/path"

def run_aws_command():
    command = f"aws s3 ls s3://{BUCKET}/{OBJECT_PATH}/ --recursive --human --sum"
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return result.stdout.splitlines(), result.stderr.splitlines()

def parse_output(output_lines):
    print("Debug: Output lines:")
    for line in output_lines:
        print(f"  {line}")

    total_size = None
    for line in reversed(output_lines):
        if "Total Size:" in line:
            print(f"Debug: Found Total Size line: {line}")
            size_str = line.split("Total Size:")[1].strip()
            size_value = float(size_str.split()[0])
            size_unit = size_str.split()[1]

            print(f"Debug: Size value: {size_value}, Size unit: {size_unit}")

            if size_unit == "MiB":
                total_size = size_value / 1024
            elif size_unit == "GiB":
                total_size = size_value
            elif size_unit == "KiB":
                total_size = size_value / (1024 * 1024)
            break

    return total_size

def generate_monthly_data(execution_date, total_size):
    first_day = execution_date.replace(day=1)
    next_month = first_day.replace(month=first_day.month % 12 + 1, day=1)
    last_day = next_month - timedelta(days=1)

    monthly_data = []
    current_day = first_day
    while current_day <= last_day:
        if current_day == execution_date.date():
            size = f"{total_size:.2f}GB"
        else:
            size = "0.00GB"
        monthly_data.append((current_day.strftime("%Y-%m-%d"), size))
        current_day += timedelta(days=1)

    return monthly_data

def save_to_csv(data, filename):
    with open(filename, 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        for row in data:
            writer.writerow(row)

def load_existing_csv(filename):
    if not os.path.exists(filename):
        return {}

    existing_data = {}
    with open(filename, 'r') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            if len(row) == 2:
                existing_data[row[0]] = row[1]
    return existing_data

def main():
    output, error = run_aws_command()

    if error:
        print("Debug: AWS command error output:")
        for line in error:
            print(f"  {line}")

    total_size = parse_output(output)

    if total_size is None:
        print("Failed to parse total size from the command output.")
        return

    print(f"Debug: Total size parsed: {total_size:.2f} GiB")

    execution_date = datetime.now()
    filename = f"client_s3-data-size_{execution_date.strftime('%Y-%m')}.csv"

    existing_data = load_existing_csv(filename)
    monthly_data = generate_monthly_data(execution_date, total_size)

    for date, size in monthly_data:
        if date in existing_data and existing_data[date] != "0.00GB":
            continue
        existing_data[date] = size

    sorted_data = sorted(existing_data.items())

    save_to_csv(sorted_data, filename)

    print(f"CSV file '{filename}' has been created/updated successfully.")

if __name__ == "__main__":
    main()

参考

最後に

s3 ls のコマンドは実行にかなり時間がかかります。

ざっくり 2TB のバケットに対して実行するのにデフォルトの Lambda のメモリ 128MB だと 5 分程度かかります。

もし Lambda で定期実行するのであれば、タイムアウトをかなり余裕を持たせるか、メモリを上げる必要があります。

もう一つの手段としては、Cloudwatch でもバケットの容量をメトリクスベースで取得できます。

少し面倒ですが、上記よりは早く値を取得できるので、候補にしてもいいと思われます。

以上、どなたかの参考になれば幸いです。

  • この記事を書いた人

緑川縁

ニートからシステムエンジニアになった人
クラウド案件をメインにやっています。
保持資格:CCNA,AWS SAA&SAP,秘書検定2級
趣味でボカロ曲作り始めました。

-IT
-, ,