1.この記事を書こうと思った背景

これまで、Terraform AWS Provider のバージョンを3.7系から4.x系に引き上げようとすると以下のissueで取り上げられているような、 aws_s3_bucket resourceでエラーとなっていた。

S3 bucket issue: Can’t configure a value for “versioning” #23125

このエラーは、Terraformの以下のガイドに記載されているとおり、aws_s3_bucket resourceの大規模な仕様変更が入ったことに起因する。

Version 4.0.0 of the AWS Provider introduces significant changes to the aws_s3_bucket resource. See S3 Bucket Refactor for more details.

出所:Terraform AWS Provider Version 4 Upgrade Guide

ところが、AWS Provider 4.9に引き上げるとエラー判定とされなくなっていた、、!? v4.0のリリース内容もびっくりだが、これもびっくりなので筆を執ることにした。

AWS Provivder 4.9.0の CHANGELOG

2.AWS Provider 4.9に引き上げるとエラー判定ではなくWARNING判定となる

  • これまでの4.x系では aws_s3_bucket resource で3.7系までの書きっぷりをすると、エラーとしてきた
  • 一方、4.9系ではWARNINGは出れどresourceは作ってくれた。それと明示的に修正が必要な箇所を指摘してくれる!!

Image from Gyazo

WARNINGが出ていて大丈夫なんですか? という点については分かっていない。Terraform が指摘してくれた箇所の修正をしたほうがいいことは間違いないだろう。(今後のアップグレードによっては、またエラー扱いとなるというこもありうる)

修正方法はこれまでの4.x系への引き上げ時におこなわれている方法と同じであり、自分の場合の修正箇所を書いておく。

3.AWS Provider 4.9へバージョンを引き上げるまでのエラー対応(自分の場合)

冒頭で取り上げたエラーや4.9で確認された WARNING を回避するためには、左記の issue の コメント に記載されているとおり、aws_s3_bucket resource にすべて詰め込むのではなく、設定したい内容に応じて別に resource を定義する必要がある。

具体的にどの resource を定義する必要があるのか?という点については、Terraform の以下のドキュメントが参考になるだろう。

Changes to S3 Bucket Drift Detection

3-1.Versioningなど設定をすべて詰め込む従来の書き方

この書き方では、上述しているエラーを引いてしまう。

terraform {
  required_version = "= 1.1.3"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      #version = "= 3.70.0" #3.7系から4.7系に引き上げたいとする
      version = "= 4.7.0"
    }
  }
}

provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = var.region
}

resource "aws_s3_bucket" "terraform_state" {
  bucket        = "bucket-for-something"
  force_destroy = true
  
  # これまでは `aws_s3_bucket` resource に設定を書いていた。以下の書き方は一例。
  versioning {
    enabled = true
  }

  server_side_encryption_configuration {
   rule {
     apply_server_side_encryption_by_default {
       sse_algorithm = "AES256"
     }
   }
  }
  acl = "private"
}

3-2.4.x系での書き方(詰め込みではなく、分割!)

terraform {
# 略
}

provider "aws" {
# 略
}

# `aws_s3_bucket` resource ではバケット名だけを指定
resource "aws_s3_bucket" "terraform_state" {
  bucket        = "bucket-for-something"
  force_destroy = true
}

# 従来 `aws_s3_bucket` resource でバケット名以外で指定していたことを、都度 resource を定義していく 
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration#apply_server_side_encryption_by_default
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.bucket
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning
resource "aws_s3_bucket_acl" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  acl    = "private"
}

resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

4. AWS Providerのバージョンを3.70.0から4.9に引き上げる作業ログ

4-1.tfstateを管理するS3を定義する aws_s3_bucket resource から引き上げる

  • AWS Providerのバージョンだけ4.9に引き上げ、他は手を加えずにplan
## AWS Provider のバージョンを引き上げたら `terraform init --upgrade`
$ terraform init --upgrade

## バージョンが4.9.0となっていることを確認
$ terraform -v
Terraform v1.1.3
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v4.9.0

## バージョンの変更以外は手を加えずplan
$ terraform plan
aws_s3_bucket.terraform_state: Refreshing state... [id=略]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # aws_s3_bucket.terraform_state has changed
  ~ resource "aws_s3_bucket" "terraform_state" {
        id                          = "略"
      + object_lock_enabled         = false
      + tags                        = {}
        # (10 unchanged attributes hidden)

      + grant {
          + id          = "略"
          + permissions = [
              + "FULL_CONTROL",
            ]
          + type        = "CanonicalUser"
        }


        # (2 unchanged blocks hidden)
    }


Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

No changes. Your infrastructure matches the configuration.

Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan:
  terraform apply -refresh-only
╷
│ Warning: Argument is deprecated
│ 
│   with aws_s3_bucket.terraform_state,
│   on terraform.tf line 19, in resource "aws_s3_bucket" "terraform_state":
│   19: resource "aws_s3_bucket" "terraform_state" {
│ 
│ Use the aws_s3_bucket_server_side_encryption_configuration resource instead
│ 
│ (and 5 more similar warnings elsewhere)
╵

WARNINGとともに修正方針を教えてくれる!!

## 左記の plan の結果より抜粋
Use the aws_s3_bucket_server_side_encryption_configuration resource instead
(and 5 more similar warnings elsewhere)
  • Changes to S3 Bucket Drift Detection を参考に resource ごとに書いていく

    • 書きっぷりは 「3-2.4.x系での書き方(詰め込みではなく、分割!」をご参照ください
  • plan & apply(applyした様子は以下)

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket_acl.terraform_state will be created
  + resource "aws_s3_bucket_acl" "terraform_state" {
      + acl    = "private"
      + bucket = "略"
      + id     = (known after apply)

      + access_control_policy {
          + grant {
              + permission = (known after apply)

              + grantee {
                  + display_name  = (known after apply)
                  + email_address = (known after apply)
                  + id            = (known after apply)
                  + type          = (known after apply)
                  + uri           = (known after apply)
                }
            }

          + owner {
              + display_name = (known after apply)
              + id           = (known after apply)
            }
        }
    }

  # aws_s3_bucket_server_side_encryption_configuration.terraform_state will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
      + bucket = "略"
      + id     = (known after apply)

      + rule {
          + apply_server_side_encryption_by_default {
              + sse_algorithm = "AES256"
            }
        }
    }

  # aws_s3_bucket_versioning.terraform_state will be created
  + resource "aws_s3_bucket_versioning" "terraform_state" {
      + bucket = "略"
      + id     = (known after apply)

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket_acl.terraform_state: Creating...
aws_s3_bucket_server_side_encryption_configuration.terraform_state: Creating...
aws_s3_bucket_versioning.terraform_state: Creating...
aws_s3_bucket_acl.terraform_state: Creation complete after 1s [id=略,private]
aws_s3_bucket_server_side_encryption_configuration.terraform_state: Creation complete after 2s [id=略]
aws_s3_bucket_versioning.terraform_state: Creation complete after 2s [id=略]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

続いて、backendを S3 としているAWSリソースの AWS Provider のバージョンを引き上げる。

4-2.backendを S3 としているAWSリソースの AWS Provider のバージョンを引き上げる

  • こちらは、AWS Provider のバージョンを指定して、terraform init --upgrade
terraform {
  required_version = "= 1.1.3"
  required_providers {
    aws = {
      source = "hashicorp/aws"
      #version = "= 3.70.0"
      version = "= 4.9.0"
    }
  }
  backend "s3" {
    bucket  = "略"
    region  = "ap-northeast-1"
    key     = "terraform.tfstate"
    encrypt = true
  }
}
## plan の結果抜粋

Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include
actions to undo or respond to these changes.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

No changes. Your infrastructure matches the configuration.

Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan:
  terraform apply -refresh-only
  • terraform apply -refresh-only をしてもやはり実体に変更なし
$ terraform apply -refresh-only
Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

略

This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record
the updated values in the Terraform state without changing any remote objects.

Would you like to update the Terraform state to reflect these detected changes?
  Terraform will write these changes to the state without modifying any real infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

無事、tfstate と S3を backend とする AWSリソースそれぞれの AWS Providerのバージョンを3.70.0から4.9に引き上げることができた

p.s. AWS Provider のアップデート、早くないっすか

アップデートが早いので Provider のバージョンの記法は自分が想定したとおりのバージョンを指定するようになっているか?確認したほうがよいと感じた。

version = ">= 3.70.0" # 右に書かれたバージョン以上(←こう書いてしまうと、右に書かれたバージョン以上であり、かつそのときの最新バージョンを指定してしまう)

version = "= 3.70.0"  # 右に書かれたバージョンのみ

version = "~> 3.70.0"  # 右に書かれたバージョンのマイナーバージョン以上、3.71未満

version = ">= 3.70.0, < 4.0.0" # 3.70.0以上、4.0.0未満)

出所:Version Constraints - Configuration Language | Terraform by HashiCorp

p.p.s Drift Detection(ドリフト検知)とは?

改めて自分の記事を読んで、 Drift Detection(ドリフト検知)とは? となったので調べた。

  • 調べた結論は、はっきりしたことはわからないけど、AWSリソースの実態とそれを定義するコードとのあいだの差分を検知するという、AWSが提供する仕組みらしいということ
  • CFn でドリフト検知が提供されるようになったというアナウンスはみかけるけど、S3 ではみかけないので、AWS Provider 4.9.0 ではどうやってドリフト検知がおこなわれているのか?についてはよくわからない