Claude Media
ClaudeでS3ライフサイクルルールを書く — Storage LensからTerraformまで

ClaudeでS3ライフサイクルルールを書く — Storage LensからTerraformまで

Storage Lensのバケット別データをclaude -pに読ませてルール案を出し、Terraformのlifecycle設定に落とします。最小保存期間の課金と128KBの既定を先に押さえる手順です。

S3のライフサイクルルールは、古いオブジェクトを安い保存クラスへ移し、不要になったものを消して保存費用を下げる仕組みです。ルール自体は短いHCLで済みます。難しいのは「どのバケットのどのプレフィックスに、何日で、どのクラスを当てるか」を決める部分と、最小保存期間の課金で逆に高くつく設定を避ける部分です。

この記事では、Storage Lensが出すバケット別の集計をClaude Codeの非対話モード(claude -p)に読ませてルール案を作り、Terraformのaws_s3_bucket_lifecycle_configurationに落とすまでを順に扱います。Cost Explorerで請求の急増原因を分けたあと、対策をコードにする段階の手順です。

先に決めておく前提 — Claudeに任せる範囲

ルール案の作成はClaudeに任せられますが、適用の判断は人に残します。ライフサイクルの設定は、追加した時点で既存のオブジェクトにも効くからです。

S3の公式ドキュメントには、現在30日で期限切れになるルールを今日追加すると、作成から30日を超えている既存オブジェクトも削除キューに入ると書かれています。移行ではオブジェクトそのものは残りますが、expiration(削除)で消えたオブジェクトは、バージョニングを有効にしていなければ戻せません。

そこで分担を次のように置きます。

  • Claude: 集計データの読み取り、ルール案(JSON)の生成、Terraformコードの下書き
  • 人: expirationを含めるかどうかの判断、terraform planの差分確認、applyの実行

applyをClaudeに実行させない構成は、Terraform全般の進め方としてClaude CodeでTerraformのコードを書く手順にまとめています。

Storage Lensで見る指標と、Claudeに渡す入力

S3 Storage Lensは、組織・アカウント・リージョン・ストレージクラス・バケット・プレフィックスの単位で保存状況を集計するサービスです。公式ドキュメントでは、コスト最適化の使い道として「不完全なマルチパートアップロードを7日以上で期限切れにするルールが無いバケット」を見つける例が挙げられています。

無料の指標とアドバンスト層があり、プレフィックス単位の集計(プレフィックスアグリゲーション)と、コンテキストに応じた推奨(contextual recommendations)はアドバンスト層の機能です。この2つはAmazon S3コンソールでのみ使えます。デフォルトのメトリクスレポートは、バケット全体の1%以上を占めるプレフィックスを集計対象とし、毎日CSVまたはParquetでS3の汎用バケットへエクスポートできます。

Claudeに読ませるのは、このエクスポートしたCSVです。ただし、ルールの日数を決めるにはオブジェクトの年齢分布が要ります。手元のレポートに年齢別の列が無い場合は、バケットごとの「作成から30日・90日・180日を超えた容量」を別途集計したCSVを足してください。分布が無い状態でClaudeに日数を決めさせると、根拠の無い数字が返ってきます。

入力CSVは次の形にそろえると、プロンプトで説明する量が減ります。

bucket,prefix,storage_class,total_gb,object_count,avg_object_kb,gb_older_30d,gb_older_90d,gb_older_180d,incomplete_mpu_gb
app-logs,logs/,STANDARD,5200,48000000,113,4900,4100,2600,12
app-media,uploads/,STANDARD,8100,2100000,3950,7200,6000,3800,0

バケット名やプレフィックスから社内のシステム構成が分かる場合は、名前を仮の記号に置き換えてから渡すと安全です。

claude -pでルール案をJSONとして出させる

非対話モードは-pフラグで使います。標準入力をそのまま読み、--output-format jsonと--json-schemaを組み合わせると、結果がstructured_outputフィールドに入ります。スクリプトの出力として扱いやすく、後段のTerraform生成に渡せます。

CIなどの定型処理では--bareを付けると、ホスト側のフックやMCPサーバー、CLAUDE.mdを読み込まずに起動します。同じ結果を毎回得たいときに向いています。ただし--bareではサブスクリプションのログインを使わないため、ANTHROPIC_API_KEYを環境変数に設定しておく必要があります。

cat storage-summary.csv | claude --bare -p \
  "このCSVのバケットごとに S3 ライフサイクルルール案を作る。
  制約: 128KB 未満のオブジェクトは移行対象にしない。
  STANDARD_IA は 30 日、GLACIER は 90 日、DEEP_ARCHIVE は 180 日を
  最小保存期間として、移行日数はそれ以上の間隔を空ける。
  expiration は提案せず、必要なら要確認として理由を書く。" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"bucket":{"type":"string"},"prefix":{"type":"string"},"transitions":{"type":"array","items":{"type":"object","properties":{"days":{"type":"integer"},"storage_class":{"type":"string"}}}},"abort_mpu_days":{"type":"integer"},"note":{"type":"string"}},"required":["bucket","prefix"]}}},"required":["rules"]}' \
  | jq '.structured_output' > rules.json

標準入力は10MBが上限です。超えると非ゼロのステータスで終了するので、大きな集計はファイルに書き出し、プロンプトでパスを指す形にします。また、structured_outputはClaudeが依頼を断った場合などに付かないことがあります。jqの結果がnullになっていないか、スクリプト側で確かめてください。

プロンプトの制約は、次節で扱う課金の落とし穴をそのまま言葉にしたものです。「expirationは提案しない」と書いておくと、削除を含むルールは人が追加する流れになります。出力は提案であって確定した設定ではないので、JSONのdaysが入力CSVの分布と合っているかは、人が突き合わせます。

Terraformのlifecycle設定に落とす

rules.jsonをもとに、Claudeへ「このJSONからaws_s3_bucket_lifecycle_configurationを書いて」と頼めば下書きが得られます。形は次のようになります(AWS providerのドキュメントにある書き方に沿った例で、Claudeの実出力ではありません)。

resource "aws_s3_bucket_lifecycle_configuration" "app_logs" {
  bucket = aws_s3_bucket.app_logs.id
 
  rule {
    id     = "abort-incomplete-mpu"
    status = "Enabled"
 
    filter {}
 
    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
 
  rule {
    id     = "logs-archive"
    status = "Enabled"
 
    filter {
      prefix = "logs/"
    }
 
    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }
 
    transition {
      days          = 120
      storage_class = "GLACIER"
    }
  }
}

書き方で踏みやすい点は4つあります。

  • prefixをruleの直下に書く形は非推奨で、将来のメジャーバージョンで削除される予定です。filterブロックの中に書きます。
  • フィルターを指定しないとバケット内のすべてのオブジェクトが対象になります。これは空のfilter {}と同じ意味です。
  • バージョニングを有効にしているバケットでは、aws_s3_bucket_versioningを先に作る必要があります。providerの例ではdepends_onでその順序を明示しています。
  • object_size_greater_thanの既定は128KBです。変えたいときはfilterで明示します。

ここまでの内容が、下書きの確認項目にもなります。Claudeにはコードだけでなく「上の4点を満たしているか自己点検して」と頼み、Checkovのような静的検査を通す流れはCheckovでのスキャン手順が参考になります。

最小保存期間の課金で起きる落とし穴

コスト削減のはずが、設定の組み方次第で請求が増えることがあります。公式ドキュメントから、ルールを書く前に確認したい点を表にします。

保存クラス最小保存期間小さいオブジェクトの扱い
S3 Standard-IA最小保存期間30日小さいオブジェクトの扱い128KB未満は128KB分を課金
S3 One Zone-IA最小保存期間30日小さいオブジェクトの扱い128KB未満は128KB分を課金
S3 Glacier Instant Retrieval最小保存期間90日小さいオブジェクトの扱い最小課金サイズは128KB
S3 Glacier Flexible Retrieval最小保存期間90日小さいオブジェクトの扱い移行時に別途40KBのオーバーヘッド
S3 Glacier Deep Archive最小保存期間180日小さいオブジェクトの扱い移行時に別途40KBのオーバーヘッド

期間より前に削除・上書き・別クラスへの移行をすると、通常の保存料金に加えて、残りの期間分が日割りで請求されます。

落とし穴

短い間隔の2段移行は、1ルールでは作れず、2ルールに分けると最小保存期間が課金される

Glacier Instant Retrievalは最小保存期間が90日です。公式ドキュメントの例では、4日後にGlacier Instant Retrievalへ移し、20日後にDeep Archiveへ移すルールは作れず、Deep Archiveへの移行は94日以降になります。2つのルールに分けて同じことをすることはできますが、その場合は最小保存期間の課金を支払います。Claudeの案に移行日数の間隔が90日未満の組み合わせがあれば、人が差し戻してください。

もう1つ見落としやすいのが移行のタイミングです。ライフサイクルによるGlacier系への移行は非同期で、設定した日と実際に移行する日の間にずれが出ることがあります。ただし課金は、ルールの条件を満たした日から移行先クラスの料金に切り替わり、最小保存期間のカウントもそこから始まります。例外はIntelligent-Tieringで、こちらは移行が完了してから課金が変わります。

小さいオブジェクトにも注意が要ります。128KB未満のオブジェクトは、既定ではどの保存クラスにも移行されません。Glacier系では移行リクエストが1オブジェクトごとに課金され、さらにオーバーヘッドが付くため、小さいファイルを大量に抱えるプレフィックスでは、移行しないほうが安く済む場合があります。公式ドキュメントも、128KB未満が混在するなら、サイズフィルターで小さいものを除外するよう勧めています。

なお、ライフサイクルによる移行に取り出し料金はかかりませんが、データを別クラスへ入れる際のリクエスト課金は発生します。

不完全マルチパートアップロードは最初に掃除できる

大きなファイルのアップロードが途中で止まると、未完了のパーツがバケットに残り、保存費用が発生し続けます。AbortIncompleteMultipartUploadのルールを入れておくと、開始から指定日数のうちに完了しなかったアップロードが中止され、パーツが削除されます。公式ドキュメントはコスト対策の推奨設定としており、例は7日です。

このルールは完了済みのアップロードやオブジェクトには作用せず、パーツの削除で最小保存期間の早期削除料金も発生しません。移行のルールより副作用が小さいので、Storage Lensで該当バケットを見つけたら、まずこの1本から入れる選び方があります。

適用前の確認 — planで読むポイント

terraform planの差分を読むとき、Claudeに「この差分で既存オブジェクトに影響するルールを列挙して」と頼めますが、最終判断は人が行います。確認の観点は次のとおりです。

  1. expirationの有無。含まれていれば、対象プレフィックスの現在の容量と作成日の分布を再度突き合わせる
  2. 移行日数の間隔が、前のクラスの最小保存期間を満たしているか
  3. 128KB未満のオブジェクトが多いプレフィックスに、移行ルールが当たっていないか
  4. バージョニングが有効なバケットで、過去バージョン用のルールが必要か(noncurrent_version_expirationなど)

ルールの効果は適用後に確かめます。公式ドキュメントには、ライフサイクルルールの動作を監視する方法として、トラブルシューティングの節が案内されています。数日後のStorage Lensのレポートで、対象プレフィックスの容量が移行先クラスへ動いたことを見る流れになります。

まとめ

Claudeが得意なのは、数十バケット分の集計を読んでルール案とHCLの下書きを一度に作る部分です。逆に、削除を含むルールの可否や、最小保存期間をまたぐ設定の採否は、材料があっても判断を委ねられません。expirationを提案させない制約をプロンプトに入れ、planの確認項目を4点に絞っておくと、速さと安全の両方を残せます。まずは不完全マルチパートの掃除ルールだけを全バケットに入れ、その後にログ系のプレフィックスから移行ルールを足す順番が、取り返しのつく範囲で効果を出しやすい進め方です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html
  3. docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-transition-general-con…
  4. docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html
  5. docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-abort-incomplete-mpu-lifecyc…
  6. docs.aws.amazon.com/AmazonS3/latest/userguide/storage_lens.html
  7. github.com/hashicorp/terraform-provider-aws/blob/main/website/docs/r/…
この記事を共有:XはてブLinkedIn