📚

Terraform S3 リファクタリング

2023/03/06に公開

概要

先日、以下の記事を書いた際の対応として、AWS Providerをv3.76.1に更新しましたが、v3.75.0以降ではv4系と同様にS3のwarningが表示されるようになりました。
v4系への更新をしなければとは思っていたのですが、s3の破壊的変更に伴うリファクタが必要となり、放置していたのですが、この機会に重い腰を上げてリファクタリングを行いました。

Warning: Argument is deprecated

  on s3.tf line 3, in resource "aws_s3_bucket" "test-bucket":
   3:   acl    = "public-read"

Use the aws_s3_bucket_acl resource instead

https://zenn.dev/tournesol014s/articles/77c51d8e17e40a

背景

  • 理由はTerraformのblogに記載されている通りですが、aws_s3_bucketリソースの中にたくさんの設定が(おそらくAWSの機能追加とともに)追加されていった結果、メンテナンス性が落ちて来ているので、バケット関連の機能を別リソースに分割した、ということのようです。(超意訳)

https://www.hashicorp.com/blog/terraform-aws-provider-4-0-refactors-s3-bucket-resource

  • この変更は破壊的変更であり、AWS Providerのv4がリリースされた時に大きな話題・議論になりました。v4.8.0までは、この新しい書き方でないとエラーになり、リファクタリングが必須でした。既存のS3リソースへ影響なくAWS Providerを更新するにはコードのリファクタリングに加えて、terraform importの実施も必要であり、多数のS3リソースを運用している人からしたらかなりの負担になることは間違いありません。そのため、v4.9.0以降は旧バージョンの書き方(aws_s3_bucketに全部設定を書く)でもエラーにならなくなりました。ただし、v5系では旧バージョンの書き方が廃止されるため、リファクタが不要になった訳ではありません。経緯等は以下のissueをご覧ください。

https://github.com/hashicorp/terraform-provider-aws/issues/23106

  • v3系においても、リファクタを推進するためか、v3.75.0以降は旧バージョンの書き方をしていると非推奨な書き方である旨のwarningが出るようになりました。

リファクタリング

  • ということで、実際にリファクタリングを実施しました。手順は、v4へのupgradeガイドがあるのですが、注意点や嵌った点があるので、実施した手順を記載します。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-4-upgrade#s3-bucket-refactor

  • v4系への更新が出来るに越したことはありませんが、他のリソースへの影響等からv3系をまだ使いたい場合もあるかと思いますので、v3系とv4系両方を試しました。

リファクタリング前

今回はシンプルな以下のケースでリファクタリングをします。

resource "aws_s3_bucket" "test-bucket" {
  bucket = "test-bucket-XXXXXXXXXX"
  acl    = "public-read"
}

v4系への更新

  • ガイド等を参照の通り、以下のようにリファクタリングします。
  resource "aws_s3_bucket" "test-bucket" {
   bucket = "test-bucket-XXXXXXXXXX"
-  acl    = "public-read"
  }

+ resource "aws_s3_bucket_acl" "test-bucket" {
+  bucket = aws_s3_bucket.test-bucket.id
+  acl    = "public-read"
+ }

  terraform {
    required_version = ">= 1.3"
    required_providers {
      aws = {
        source  = "hashicorp/aws"
-       version = "3.40.0"
+       version = "4.57.0"
      }
    }
  }
  • AWS Providerを更新するので、init -upgradeをした後にplanを実行します。
% terraform init -upgrade
% terraform plan
  # aws_s3_bucket_acl.test-bucket will be created
  + resource "aws_s3_bucket_acl" "test-bucket" {
      + acl    = "public-read"
      + bucket = "test-bucket-XXXXXXXXXX"
      ....
    }
Plan: 1 to add, 0 to change, 0 to destroy.
  • すると、リファクタリングにより追加したリソースが作成される表示になるので、importを行います。
% terraform import aws_s3_bucket_acl.test-bucket test-bucket-XXXXXXXXXX,public-read
  • import後にplanを実行すると差分が出なくなっています。
% terraform plan
No changes. Your infrastructure matches the configuration.

v3系(v3.75.0以降)への更新

  • 基本的にはv4系と同じですが、aws_s3_bucketでignore changesを追加しています。importの手順等は同じです。
  resource "aws_s3_bucket" "test-bucket" {
   bucket = "test-bucket-XXXXXXXXXX"
-  acl    = "public-read"

+   lifecycle {
+     ignore_changes = [
+       acl
+     ]
+   }
  }

+ resource "aws_s3_bucket_acl" "test-bucket" {
+  bucket = aws_s3_bucket.test-bucket.id
+  acl    = "public-read"
+ }

  terraform {
    required_version = ">= 1.3"
    required_providers {
      aws = {
        source  = "hashicorp/aws"
-       version = "3.40.0"
+       version = "3.76.1"
      }
    }
  }
  • 仮に、ignore_changesを追加しない場合、以下の通り差分が検出され、このままapplyすると意図しない変更が反映されてしまいます。これは、v3系特有の挙動のようで、v3系のドキュメントを見ると記載があります。ただし、igonore_changesにaclを追加すると、aclの利用は非推奨であるとwarningが出てしまいます。。。(v4系に早く上げろという話ではありますが)
  # aws_s3_bucket.test-bucket will be updated in-place
  ~ resource "aws_s3_bucket" "test-bucket" {
      ~ acl                         = "public-read" -> "private"
        id                          = "test-bucket-XXXXXXXXXX"
    }

https://registry.terraform.io/providers/hashicorp/aws/3.75.2/docs/resources/s3_bucket_acl#usage-notes

  • acl以外のリソースについても同様にignore_changesの追加が必要です。具体的に何をignore_changesする必要があるかは、各リソースのv3系のドキュメントを参照してください。(v4系には記載がありません)

補足

  • デフォルト値と同じ内容をコード上、明示的に指定している場合は、ignore_changes追加しなくても差分は出ません。例えば、aclの場合は以下のケースです。
resource "aws_s3_bucket" "test-bucket" {
  bucket = "test-bucket-XXXXXXXXXX"
  acl    = "private"
}
  • また、リファクタリング後にコード上、明示的にその値をセットしない場合は、terraform importも不要で、aws_s3_bucketから該当の設定を消すだけでOKです。
  resource "aws_s3_bucket" "test-bucket" {
   bucket = "test-bucket-XXXXXXXXXX"
-  acl    = "private"
  }

Discussion