AWSの複数リージョンを扱うTerraformを0.11系から0.12系に更新するときにproviderまわりでハマった話

はい。 Terraformの小ネタです。

この記事は第二のドワンゴ Advent Calendar 2019の参加記事です。*1 ドワンゴ Advent Calendar 2019もありますのでそちらも併せてご覧ください。

Terraformの0.12系がリリースされてから半年ちょっと経ちましたね。

Terraformを0.11系以前から0.12系へアップデートする作業を公私合わせて片手に収まらないくらいの回数なぜか行っているのですが、複数リージョンを扱うようなmoduleを0.12対応させる際に微妙に手間取った点があるので、これはきっと他にも同じような状況の人がいるに違いない*2ということで書きます。

結論から言うと

  • 0.12系ではaliasで別名を付けたproviderをmoduleから暗黙には使えなくなった
  • module内でaliasを張られたプロバイダを使うにはmoduleを統合する側のtfファイルから明示的に渡す必要がある
  • 複数providerを受け取る側のmoduleでも空のprovider記述をして存在を示す必要がある

詳しく

この事象に遭遇するのは、terraform公式のRepository Structureのページで解説されているような、modulesディレクトリの中で各種リソースを定義するmoduleを作り、それを各環境ごとに作成したディレクトリの中で組み合わせているディレクトリ構成のterraformにおいて、SESなどの特定リージョンにしか存在しないサービスを使っているときや、cloudfrontで使うSSL証明書ACMで発行しているときだと思います。

具体的には、cloudfrontはどのリージョンのエンドポイントからも操作することができますが、cloudfrontで使用するACM発行のSSL証明書バージニア北部リージョンで発行しなければなりません。

また、SESもバージニア北部、オレゴンアイルランドでしかサービス提供されていないため、日本のサービスが使おうとするとだいたいバージニア北部かオレゴンを使うことになるでしょう。

とはいえ日本のサービスはほとんどのリソースを東京に置いているわけで、ここでterraform上で複数のリージョンを扱う必要が出てきます。

0.11系までは単に呼び出す環境側で
environments/dev/config.tf (0.11の構文)

provider "aws" {
  region  = "${var.region}"
  version = "1.31.0"
}

provider "aws" {
  alias  = "the_other_region"
  region  = "${var.the_other_region}"
}

としてaliasが書かれた二つ目のproviderを作り、参照される側のmoduleの中でも
modules/example/s3.tf (0.11の構文)

resource "aws_s3_bucket" "the_other_regions_bucket" {
    provider = "aws.the_other_region"
    bucket   = "the-other-regions-bucket"
    region   = "${var.the_other_region}"
}

とすればvar.the_other_regionで設定したリージョンにthe_other_regions_bucketを作ってくれました。

が、これを0.12系の構文に0.12upgradeサブコマンドなどを使って書き換えるとinitが通って一安心と思いきや、

To work with module.example.aws_s3_bucket.the_other_regions_bucket its original provider
configuration at module.example.provider.aws.the_other_region is required, but it
has been removed. This occurs when a provider configuration is removed while
objects created by that provider still exist in the state. Re-add the provider
configuration to destroy module.example.aws_s3_bucket.the_other_regions_bucket, after
which you can remove the provider configuration again.

などと表示されてplanが通りません。

Terraformのmultiple providerに関するセクションを参考に
environments/dev/modules.tf (0.12の構文)

module "example" {
  source = "../../modules/example"
  the_other_region = var.the_other_region

  providers = {
      aws = aws
      aws.the_other_region = aws.the_other_region
  }

としてもまだダメ。

実は上で示したエラーメッセージをよく見ると書いてあるのですが、module.example.provider.aws.the_other_regionと書いてある通り、moduleの側にもprovider.aws.the_other_regionが存在することを示すための記述が要ります。下記のように。

modules/example/variables.tf

provider "aws" {
}

provider "aws" {
  alias   = "the_other_region"
}
【以降略】

これで解決。

というわけで

今回の例のような、同種のproviderを複数個、aliasを付けて使用するようなTerraformを0.12系に更新する際、0.12upgradeサブコマンドはいろいろよしなに書き換えてくれるものの、providerの受け渡しに関する記述まではさすがにやってくれないので注意、という話でした。

余談

terraform全体としては複数のリージョンを扱うが、一つのmodule内では一つのリージョンしか扱わない場合、
environments/dev/modules.tf (0.12の構文)

module "example" {
  source = "../../modules/example"
  the_other_region = var.the_other_region

  providers = {
      aws =  aws.the_other_region
  }
}

とも書けます。

この場合modules/example/s3.tfaws_s3_bucket.the_other_regions_bucketのprovider指定とmodules/example/variables.tfの空のprovider定義を書く必要はありません。

というか多分providerが暗黙に渡されていた問題の解決とmoduleの再利用性を高めるという二つの目的のもとにこうしたのではないかな…という気がします。

*1:去年のアドベントカレンダーもTerraformネタだったな俺…

*2:また数か月後の未来の自分かもしれない…と思ったけど手が届く範囲はさすがに0.12系にしきったはず