Terraform AWS Provider version 6がリリースされ、複数リージョンへの展開がかなり簡単になりました

Terraform AWS Provider version 6がリリースされ、複数リージョンへの展開がかなり簡単になりました

Clock Icon2025.07.08

2025年6月19日にTerraformのAWS Providerの version 6がリリースされました。6月27日には 6.1.0、7月3日に6.2.0もリリースされています。

本エントリでは version6の変更点についてご紹介します。

複数リージョンへの展開がかなり簡単になりました

これまで

AWS provider version 5までは、複数のリージョンにリソースをプロビジョニングしたい場合は、各リージョンごとに providerブロックを定義する必要がありました。

# デフォルトプロバイダー(us-east-1)
provider "aws" {
  region = "us-east-1"
}

# us-west-2用のエイリアス付きプロバイダー
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

そして リソース・データソースブロック内のprovider引数にてエイリアスを指定することで、リソースであればそのリージョンにリソースをプロビジョニング、データソースであればそのリージョンに存在するリソースを参照することができました。

# us-east-1にS3バケットを作成
# ※ エイリアス未指定の場合、デフォルトプロバイダーが使用される
resource "aws_s3_bucket" "main" {
  bucket = "my-main-bucket"
}

# us-west-2にS3バケットを作成(aliasを使用)
resource "aws_s3_bucket" "west" {
  provider = aws.west
  bucket   = "my-west-bucket"
}

# us-east-1のデフォルトVPCを参照
# ※ エイリアス未指定の場合、デフォルトプロバイダーが使用される
data "aws_vpc" "main" {
  default = true
}

# us-west-2のデフォルトVPCを参照(aliasを使用)
data "aws_vpc" "west" {
  provider = aws.west
  default  = true
}

上記のように使うリージョンが2つくらいだったらさほど問題ではないです。ですがもっとたくさんのリージョンを使いたい場合、コードが冗長になり可読性が下がるという問題がありました。

具体的な例としては、GuardDutyのような全リージョンで有効化することが推奨されているサービスのIaCをTerraformでやりたい場合が考えられます。以下サンプルコードです。

provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "us-east-2"
  region = "us-east-2"
}

provider "aws" {
  alias  = "us-west-1"
  region = "us-west-1"
}

provider "aws" {
  alias  = "us-west-2"
  region = "us-west-2"
}

provider "aws" {
  alias  = "ap-northeast-1"
  region = "ap-northeast-1"
}

resource "aws_guardduty_detector" "us_east_1" {
  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

resource "aws_guardduty_detector" "us_east_2" {
  provider = aws.us-east-2

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

resource "aws_guardduty_detector" "us_west_1" {
  provider = aws.us-west-1

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

resource "aws_guardduty_detector" "us_west_2" {
  provider = aws.us-west-2

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

resource "aws_guardduty_detector" "ap_northeast_1" {
  provider = aws.ap-northeast-1

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

長いですね。しかもほぼ同じコードが続くという…

「for_eachを使えばもっとコンパクトにできるのでは?」とお考えの方、鋭いです。が、providerブロックではfor_eachを使う事ができません。

locals {
  target_regions = [
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2",
    "ap-northeast-1",
  ]
}

provider "aws" {
  for_each = toset(local.target_regions)

  alias = (each.value == "us-east-1") ? null : each.value
  region = each.value
}
# エラーになります
% terraform apply
╷
│ Error: Invalid provider configuration alias
│ 
│ An alias must be a valid name. A name must start with a letter or underscore and may contain only letters, digits, underscores, and
│ dashes.
╵
╷
│ Error: Reserved argument name in provider block
│ 
│   on main.tf line 24, in provider "aws":24:   for_each = toset(local.target_regions)
│ 
│ The provider argument name "for_each" is reserved for use by Terraform in a future version.
╵
╷
│ Error: Variables not allowed
│ 
│   on main.tf line 26, in provider "aws":26:   alias = (each.value == "us-east-1") ? null : each.value
│ 
│ Variables may not be used here.
╵
╷
│ Error: Unsuitable value type
│ 
│   on main.tf line 26, in provider "aws":26:   alias = (each.value == "us-east-1") ? null : each.value
│ 
│ Unsuitable value: value must be known

また、 aws_guardduty_detector リソースブロックでも、for_eachが使えません。 providerメタ引数に対して for_eachが使えないのです。

resource "aws_guardduty_detector" "main" {
  for_each = local.target_regions

  provider = (each.value == "us-east-1") ? null : each.value

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}
# エラーになります
% terraform apply   
╷
│ Error: Invalid provider configuration reference
│ 
│   on main.tf line 58, in resource "aws_guardduty_detector" "main":58:   provider = (each.value == "us-east-1") ? null : "aws.${each.value}"
│ 
│ The provider argument requires a provider type name, optionally followed by a period and then a configuration alias.

これから

version 6より、各リソース・データソースブロック内でリージョン指定ができるようになりました。

まず、providerブロックは一つだけで良くなりました。デフォルトリージョンを指定します。

provider "aws" {
  region = "us-east-1"
}

デフォルトリージョン以外でリソースもしくはデータソースを使いたい場合は、
各リソースブロック・データソースブロック に region 引数が追加されているのでこれを使います。

resource "aws_guardduty_detector" "us_east_2" {
  region = "us-east-2"

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

以下 plan結果です。 region はド頭に記載して欲しいですが、仕方ないですかね…

% terraform plan

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_guardduty_detector.us_east_2 will be created
  + resource "aws_guardduty_detector" "us_east_2" {
      + account_id                   = (known after apply)
      + arn                          = (known after apply)
      + enable                       = true
      + finding_publishing_frequency = (known after apply)
      + id                           = (known after apply)
      + region                       = "us-east-2"
      + tags_all                     = (known after apply)

      + datasources {
          + kubernetes {
              + audit_logs {
                  + enable = false
                }
            }
          + malware_protection {
              + scan_ec2_instance_with_findings {
                  + ebs_volumes {
                      + enable = true
                    }
                }
            }
          + s3_logs {
              + enable = true
            }
        }
    }

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

そして、(これまでの provider meta argumentとは違って、) for_eachが使えます!

locals {
  target_regions = [
    "us-east-1",
    "us-east-2",
    "us-west-1",
    "us-west-2",
    "ap-northeast-1",
  ]
}

resource "aws_guardduty_detector" "main" {
  for_each = toset(local.target_regions)

  region = each.value

  enable = true

  datasources {
    s3_logs {
      enable = true
    }
    kubernetes {
      audit_logs {
        enable = false
      }
    }
    malware_protection {
      scan_ec2_instance_with_findings {
        ebs_volumes {
          enable = true
        }
      }
    }
  }
}

デフォルトリージョンに対しては region 引数要らないので以下でも良いですが、plan結果でdiffは出なかったのでどちらでも良いみたいです。

  resource "aws_guardduty_detector" "main" {
    for_each = toset(local.target_regions)

-   region = each.value
+   region = (each.value == "us-east-1") ? null : each.value

    enable = true
(以下略)

また、providerブロックの定義をひとつだけにできたことで、Terraformのメモリ使用料量も減らすことができたそうです。

移行方法

version 6でも 5までの書き方がそのまま使えます。具体的には

  • alias引数を使って複数のprovider ブロックを書く
  • 各リソースブロック・データソースブロックで provider引数を指定する

がそのまま使えるので、単に AWS providerのversion指定をあげて terraform init -upgradeを実行すればよいです。

  terraform {
    required_version = "= 1.12.2"

    required_providers {
      aws = {
        source  = "hashicorp/aws"
-       version = "~> 5.100"
+       version = "~> 6.2"
      }
    }
  }

その後、 movedブロックを使ってリファクタリングしていきましょう。

※ ただ、公式ブログには、リファクタリングの前に terraform plan -refresh-onlyterraform apply -refresh-onlyが必要と書かれています。ですので慎重にやりたい場合はこれらのコマンドも実行するほうが無難でしょう。今回の私のサンプルコードの場合は特に必要ありませんでした。


+ locals {
+   target_regions = [
+     "us-east-1",
+     "us-east-2",
+     "us-west-1",
+     "us-west-2",
+     "ap-northeast-1",
+   ]
+ }

  provider "aws" {
    region = "us-east-1"
  }

- provider "aws" {
-   alias  = "us-east-2"
-   region = "us-east-2"
- }
-
- provider "aws" {
-   alias  = "us-west-1"
-   region = "us-west-1"
- }
-
- provider "aws" {
-   alias  = "us-west-2"
-   region = "us-west-2"
- }
-
- provider "aws" {
-   alias  = "ap-northeast-1"
-   region = "ap-northeast-1"
- }

+ resource "aws_guardduty_detector" "main" {
+   for_each = toset(local.target_regions)
+ 
+   region = each.value
+ 
+   enable = true
+ 
+   datasources {
+     s3_logs {
+       enable = true
+     }
+     kubernetes {
+       audit_logs {
+         enable = false
+       }
+     }
+     malware_protection {
+       scan_ec2_instance_with_findings {
+         ebs_volumes {
+           enable = true
+         }
+       }
+     }
+   }
+ }
+ 
+ moved {
+   from = aws_guardduty_detector.us_east_1
+   to = aws_guardduty_detector.main["us-east-1"]
+ }
+ 
+ moved {
+   from = aws_guardduty_detector.us_east_2
+   to = aws_guardduty_detector.main["us-east-2"]
+ }
+ 
+ moved {
+   from = aws_guardduty_detector.us_west_1
+   to = aws_guardduty_detector.main["us-west-1"]
+ }
+ 
+ moved {
+   from = aws_guardduty_detector.us_west_2
+   to = aws_guardduty_detector.main["us-west-2"]
+ }
+ 
+ moved {
+   from = aws_guardduty_detector.ap_northeast_1
+   to = aws_guardduty_detector.main["ap-northeast-1"]
+ }

- resource "aws_guardduty_detector" "us_east_1" {
-   enable = true
-
-   datasources {
-     s3_logs {
-       enable = true
-     }
-     kubernetes {
-       audit_logs {
-         enable = false
-       }
-     }
-     malware_protection {
-       scan_ec2_instance_with_findings {
-         ebs_volumes {
-           enable = true
-         }
-       }
-     }
-   }
- }
-
- resource "aws_guardduty_detector" "us_east_2" {
-   provider = aws.us-east-2
-
-   enable = true
-
-   datasources {
-     s3_logs {
-       enable = true
-     }
-     kubernetes {
-       audit_logs {
-         enable = false
-       }
-     }
-     malware_protection {
-       scan_ec2_instance_with_findings {
-         ebs_volumes {
-           enable = true
-         }
-       }
-     }
-   }
- }
-
- resource "aws_guardduty_detector" "us_west_1" {
-   provider = aws.us-west-1
-
-   enable = true
-
-   datasources {
-     s3_logs {
-       enable = true
-     }
-     kubernetes {
-       audit_logs {
-         enable = false
-       }
-     }
-     malware_protection {
-       scan_ec2_instance_with_findings {
-         ebs_volumes {
-           enable = true
-         }
-       }
-     }
-   }
- }
-
- resource "aws_guardduty_detector" "us_west_2" {
-   provider = aws.us-west-2
-
-   enable = true
-
-   datasources {
-     s3_logs {
-       enable = true
-     }
-     kubernetes {
-       audit_logs {
-         enable = false
-       }
-     }
-     malware_protection {
-       scan_ec2_instance_with_findings {
-         ebs_volumes {
-           enable = true
-         }
-       }
-     }
-   }
- }
-
- resource "aws_guardduty_detector" "ap_northeast_1" {
-   provider = aws.ap-northeast-1
-
-   enable = true
-
-   datasources {
-     s3_logs {
-       enable = true
-     }
-     kubernetes {
-       audit_logs {
-         enable = false
-       }
-     }
-     malware_protection {
-       scan_ec2_instance_with_findings {
-         ebs_volumes {
-           enable = true
-         }
-       }
-     }
-   }
- }
% terraform apply
aws_guardduty_detector.main["ap-northeast-1"]: Refreshing state... [id=cccbf250f5435e518605fc52ce8f1898]
aws_guardduty_detector.main["us-east-2"]: Refreshing state... [id=d8cbf250f860b978e47fba274e59bbc3]
aws_guardduty_detector.main["us-west-2"]: Refreshing state... [id=d6cbf250f77a92ede9ed4af886326f03]
aws_guardduty_detector.main["us-east-1"]: Refreshing state... [id=a6cbf250f810a6cb51932b1f760bf6d4]
aws_guardduty_detector.main["us-west-1"]: Refreshing state... [id=a6cbf250f6b51407ce25053fcfa50ff1]

Terraform will perform the following actions:

  # aws_guardduty_detector.ap_northeast_1 has moved to aws_guardduty_detector.main["ap-northeast-1"]
    resource "aws_guardduty_detector" "main" {
        id                           = "cccbf250f5435e518605fc52ce8f1898"
        tags                         = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_guardduty_detector.us_east_1 has moved to aws_guardduty_detector.main["us-east-1"]
    resource "aws_guardduty_detector" "main" {
        id                           = "a6cbf250f810a6cb51932b1f760bf6d4"
        tags                         = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_guardduty_detector.us_east_2 has moved to aws_guardduty_detector.main["us-east-2"]
    resource "aws_guardduty_detector" "main" {
        id                           = "d8cbf250f860b978e47fba274e59bbc3"
        tags                         = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_guardduty_detector.us_west_1 has moved to aws_guardduty_detector.main["us-west-1"]
    resource "aws_guardduty_detector" "main" {
        id                           = "a6cbf250f6b51407ce25053fcfa50ff1"
        tags                         = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

  # aws_guardduty_detector.us_west_2 has moved to aws_guardduty_detector.main["us-west-2"]
    resource "aws_guardduty_detector" "main" {
        id                           = "d6cbf250f77a92ede9ed4af886326f03"
        tags                         = {}
        # (6 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

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

movedブロックでは(Terraform version 1.12の現時点では) for_eachを使えないので、地道に1リージョン分ずつ書いていくしか無いですね…

破壊的変更がたくさん

  • OpsWorks, SimpleDBなどライフサイクル終了(EOL)を迎えたサービスに関連する多くの属性やリソースが削除されました。
  • 前項で紹介した通り、全リソース・データソースで region 引数が利用可能になったことから波及した変更が複数のリソース・データソースで発生しています。例えば Data Source: aws_regionname引数が Deprecatedになり、代わりに regionを使うことが推奨になりました。

などなど。詳しくは以下のUpgrade Guideをご確認ください!

S3バケットの破壊的変更はどうなったのか

結論から言うと変更なしのようです。これまでの経緯をご紹介します。

  1. Version 4のGA時に、S3バケット(aws_s3_bucketリソース)にbreaking change(破壊的変更)がありました。つまりVersion 3と同じコードだとエラーになるようになりました。
  2. その後Version 4.9.0にて、breaking changeは撤回つまりversion 3と同じコードでもエラーにならなくなりました。ただしDeprecatedとしてWarningが表示されます。
  3. Version 5にて再び旧来の(Version 3までの)コードだとエラーになる予定でした。
  4. が、Version5リリース時には上記対応は見送られました。つまり旧来のコードではエラーにならず、deprecatedのwarningが表示されるだけです。
  5. 改めてVersion6で対応される予定となっていました。

で、今回 Version 6がリリースされたわけですが、このS3バケットの破壊的変更については変更なし、つまり 旧来の(Version 3までの)コードでもエラーにならずdeprecatedのwarningが表示されるだけのままです。

Upgrade Guideのaws_s3_bucketに特に記載がありませんし、V6のaws_s3_bucketリソースのドキュメントにも Deprecatedの引数が多数記載されています。以下はドキュメントのキャプチャです。

aws_s3_bucket.png

参考情報

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.