TerraformでS3バケットを管理する方法|バージョニング・ライフサイクル・暗号化のリソース分割設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Terraform > TerraformでS3バケットを管理する方法|バージョニング・ライフサイクル・暗号化のリソース分割設計
「S3バケットの設定をTerraformで書き直そうとしたら、aws_s3_bucket内にversioning { } を書いてもplanで警告が出るようになった」——そんな経験をした方は多いはずです。

AWS Provider v4以降、S3バケットの設定リソースが大きく再設計されました。v3以前は1つのリソースブロックに詰め込んでいたバージョニング・ライフサイクル・暗号化の設定が、それぞれ独立したリソースとして分割されています。この変更を知らずに書くと、現場での terraform apply でエラーが連発する原因になります。

この記事では、AWS Provider v4以降の分割設計に対応したS3バケット管理の実践的な書き方を解説します。バージョニングの有効化からライフサイクルルール・サーバーサイド暗号化まで、本番環境での設計判断とよくあるトラブルの切り分け方を整理します。

この記事のポイント

・AWS Provider v4以降ではaws_s3_bucket_*リソースで属性を分割して定義する
・バージョニング有効化後にライフサイクルルールを設定するにはdepends_onが必要
・SSE-S3はbucket_key_enabled = trueでKMS APIコール料金を削減できる
・force_destroyはオブジェクト全削除を伴うため本番バケットへの使用は要注意



「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜS3バケット設定をリソース分割で書くのか

AWS Provider v3以前では、次のようにaws_s3_bucketリソース1つにバージョニングやライフサイクルをまとめて書くことが一般的でした。

# AWS Provider v3系の旧スタイル(v4以降は非推奨) resource "aws_s3_bucket" "example" { bucket = "my-terraform-bucket" versioning { enabled = true } lifecycle_rule { id = "delete-old-versions" enabled = true noncurrent_version_expiration { days = 30 } } server_side_encryption_configuration { rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } }

v4以降でこのコードを実行すると、terraform planで次のような警告が表示されます。

# terraform plan 実行時の警告例 Warning: Argument is deprecated with aws_s3_bucket.example, on main.tf line 8, in resource "aws_s3_bucket" "example": 8: versioning { Use the aws_s3_bucket_versioning resource instead.

Terraform公式がこの分割設計を採用した主な理由は以下の通りです。
・個別設定の変更差分が見やすくなる(monolithicブロックだとdiffが巨大になる)
・各機能の依存関係をdepends_onで明示できる(特にバージョニングとライフサイクルの順序保証)
・リソース削除・再作成の影響範囲が限定できる

aws_s3_bucketと分割リソースの基本構成

1. メインバケットリソースの定義

バケット本体の定義はシンプルになっています。bucket名とtagsのみを定義し、個々の設定は後続の分割リソース側に書くのが現在の実務スタンダードです。

resource "aws_s3_bucket" "example" { bucket = "my-terraform-bucket-prod" tags = { Environment = "production" ManagedBy = "terraform" } }

2. パブリックアクセスブロックの設定

本番環境のバケットは原則としてパブリックアクセスを全遮断します。aws_s3_bucket_public_access_block(パブリックアクセスブロック)は独立したリソースとして定義します。

resource "aws_s3_bucket_public_access_block" "example" { bucket = aws_s3_bucket.example.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true }

4つのフラグをすべてtrueにしないと、個別に抜け穴が生じます。静的サイトホスティングなど公開が必要なユースケースでのみ、対応フラグをfalseに設定してください。

3. バージョニングの有効化

バージョニングはaws_s3_bucket_versioningリソースで設定します。一度有効にしたバージョニングは「無効化」ではなく「停止(Suspended)」にしか戻せない点に注意してください。

resource "aws_s3_bucket_versioning" "example" { bucket = aws_s3_bucket.example.id versioning_configuration { status = "Enabled" # status = "Suspended" # 停止(削除はできない) } }

ライフサイクルルールで旧バージョンを自動削除する

バージョニングを有効にするとオブジェクトの旧バージョンが蓄積し続け、ストレージコストが増大します。aws_s3_bucket_lifecycle_configuration(ライフサイクル設定)で旧バージョンの自動削除や低コストストレージクラスへの移行を定義することが、本番運用での鉄則です。

Terraformを使ったS3設計パターンの全体像は、Terraform実践記事一覧でもまとめて解説しています。

1. 旧バージョンの自動削除ルール

【重要】ライフサイクルルールを定義する前に、バージョニングが有効になっている必要があります。depends_onでaws_s3_bucket_versioningへの依存を明示しないと、plan/applyの実行順序が保証されず、適用が不安定になります。

resource "aws_s3_bucket_lifecycle_configuration" "example" { bucket = aws_s3_bucket.example.id # バージョニングが有効になった後に設定するためdepends_onが必須 depends_on = [aws_s3_bucket_versioning.example] rule { id = "delete-old-versions" status = "Enabled" # 最新版以外のバージョンを30日後に削除 noncurrent_version_expiration { noncurrent_days = 30 } # 不完全なマルチパートアップロードを7日後に削除 abort_incomplete_multipart_upload { days_after_initiation = 7 } } }

2. ストレージクラス移行(Standard-IA / Glacier)

アクセス頻度が低いオブジェクトをStandard-IA(低頻度アクセス)やGlacierに移行することで、保存コストを50~90%削減できます。

rule { id = "transition-lifecycle" status = "Enabled" # 90日後にStandard-IAへ移行 transition { days = 90 storage_class = "STANDARD_IA" } # 365日後にGlacier Flexible Retrievalへ移行 transition { days = 365 storage_class = "GLACIER" } # 旧バージョンも同様に移行 noncurrent_version_transition { noncurrent_days = 30 storage_class = "STANDARD_IA" } }

Standard-IAは最小ストレージ期間が128KB・30日のため、小さなオブジェクトや短期間しか保存しないデータへの適用は逆にコスト増になります。事前にオブジェクトのサイズ分布と保存期間を確認してから設定してください。

サーバーサイド暗号化(SSE)の設定パターン

1. SSE-S3(AES-256)で全オブジェクトを暗号化する

aws_s3_bucket_server_side_encryption_configuration(サーバーサイド暗号化設定)でSSE-S3を有効にします。SSE-S3はS3が管理するキーを使うため、KMSと比べてコストがかかりません。

resource "aws_s3_bucket_server_side_encryption_configuration" "example" { bucket = aws_s3_bucket.example.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } # S3バケットキーを有効化するとKMS APIコールを削減できる bucket_key_enabled = true } }

2. SSE-KMS(KMSカスタムキー)で証跡管理を強化する

コンプライアンス要件でKMSキーへのアクセス履歴(CloudTrail)が求められる場合は、aws:kmsアルゴリズムを使います。

resource "aws_kms_key" "s3" { description = "KMS key for S3 server-side encryption" deletion_window_in_days = 10 enable_key_rotation = true } resource "aws_kms_alias" "s3" { name = "alias/my-s3-key" target_key_id = aws_kms_key.s3.key_id } resource "aws_s3_bucket_server_side_encryption_configuration" "kms" { bucket = aws_s3_bucket.example.id rule { apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" kms_master_key_id = aws_kms_key.s3.arn } bucket_key_enabled = true } }

bucket_key_enabled = trueを忘れると、オブジェクトのPUT/GETごとにKMS APIが呼ばれ、高トラフィックなバケットでは料金が予想外に膨らみます。これは現場でよく見かける設計ミスです。

トラブルシュートとよくある設計ミス

1. 「BucketNotEmpty」エラーでterraform destroyが止まる

オブジェクトが入っているバケットをそのままdestroyしようとすると、次のようなエラーで止まります。

# terraform destroy 実行時のエラー例 Error: deleting Amazon S3 Bucket (my-terraform-bucket-prod): BucketNotEmpty: The bucket you tried to delete is not empty. You must delete all versions in the bucket. status code: 409, request id: XXXX, host id: YYYY

解決策はaws_s3_bucketリソースにforce_destroy = trueを追加することです。これを設定するとterraform destroy時にバケット内のすべてのオブジェクトと旧バージョンが自動削除されます。

要注意なのは、本番データを保管しているバケットに安易に設定してはいけない点です。force_destroy = trueは開発・テスト環境専用の設定と位置づけ、本番バケットでは手動で削除操作を行う運用を推奨します。

resource "aws_s3_bucket" "dev" { bucket = "my-dev-bucket" # 開発環境専用。本番バケットには設定しないこと force_destroy = true }

2. ライフサイクルルールがなぜか適用されない

ライフサイクルルールを書いても旧バージョンが削除されない場合、最初に確認すべきは「バージョニングが有効になっているか」です。depends_onを付けずにterraform applyすると、バージョニングとライフサイクル設定が並行して適用されることがあります。

また、ライフサイクルルールはAWS側で反映されるまで最大24時間程度かかる場合があります。apply直後に削除されないことだけで「適用されていない」と判断するのは早計です。AWS CLIでルールの登録状態を確認してから判断してください。

# ライフサイクルルールの登録状態を確認 # aws s3api get-bucket-lifecycle-configuration # --bucket my-terraform-bucket-prod { "Rules": [ { "ID": "delete-old-versions", "Status": "Enabled", "NoncurrentVersionExpiration": { "NoncurrentDays": 30 } } ] }

ルールのStatusがEnabledになっているにもかかわらず動作しない場合は、オブジェクトにバージョニング以前のnull versionが残っていないかも確認してください。null versionはnoncurrent_version_expirationの対象にならない場合があります。

本記事のまとめ

TerraformでS3バケットを管理する際のポイントをまとめます。
設定項目 使用リソース 主な注意点
バケット本体 aws_s3_bucket v4以降はbucket名・tagsのみ記述する
パブリックアクセス遮断 aws_s3_bucket_public_access_block 4つのフラグをすべてtrueにする
バージョニング aws_s3_bucket_versioning 有効化後は削除できない(Suspendedのみ)
ライフサイクル aws_s3_bucket_lifecycle_configuration depends_on = [versioning] が必須
暗号化(SSE-S3) aws_s3_bucket_server_side_encryption_configuration bucket_key_enabled = trueでKMS料金を削減
バケット強制削除 force_destroy = true(aws_s3_bucket内) 開発環境専用。本番バケットへの使用は禁止
AWS Provider v4以降のリソース分割は「面倒になった」ではなく、「変更の影響範囲を限定できる設計に進化した」と捉えることで、運用品質が上がります。depends_onによる依存関係の明示とforce_destroyの危険性を押さえて、安全なS3インフラ管理を実現してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、
Terraformを含むインフラIaC設計の実践コースはこちら。
>> Terraform実践記事一覧を見る

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。