CLOVER🍀

That was when it all began.

Terraformでリソースやモジュールに適用するProviderを、エイリアスで指定する

これは、なにをしたくて書いたもの?

Terraformで、同じ種類のProviderであってもエイリアスを使って異なる構成のProviderとして定義し、適用するリソースや
モジュールに応じてどのProviderを使うか選択することができます。

Provider Configuration / alias: Multiple Provider Configurations

Resources / provider: Selecting a Non-default Provider Configuration

Modules / Providers Within Modules

機能としてあることは知っていたのですが、使ったことがなかったので、今回試してみることにしました。

お題

MySQL Providerを使って、データベースを作ることにします。

Provider: MySQL - Terraform by HashiCorp

MySQL: mysql_database - Terraform by HashiCorp

この時に、MySQL Providerを複数構成して、異なるMySQLサーバーにデータベースを作成していってみましょう。

環境

今回の環境は、こちら。

$ terraform version
Terraform v0.13.4
+ provider registry.terraform.io/terraform-providers/mysql v1.9.0

MySQLは8.0.21を使用し、Providerごとに172.17.0.2、172.17.0.3の2つのMySQL Serverを用意しているものとします。

リソースごとにProviderを指定する

まずは、リソースごとにProviderを指定してみましょう。

Provider Configuration / alias: Multiple Provider Configurations

Resources / provider: Selecting a Non-default Provider Configuration

こんな感じで。
main.tf

terraform {
  required_version = "0.13.4"

  required_providers {

    mysql = {
      source  = "terraform-providers/mysql"
      version = "1.9.0"
    }
  }
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

resource "mysql_database" "app1" {
  name = "my_database"
}

resource "mysql_database" "app2" {
  provider = mysql.secondary
  name     = "other_database"
}

Providerの定義を、2つ用意しました。

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

aliasがついていないものを、Default Providerと呼ぶようです。

Default Provider Configurations

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

リソースやモジュールで、明示的に利用するProviderが指定されていない場合は、このDefault Providerが使用されます。

通常は、こちらを使用しているでしょうね。

もうひとつは、aliasを指定した代替のProvider。

Referring to Alternate Provider Configurations

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

今回は、こちらをsecondaryと名付けました。

あとは、リソースに対してProviderを指定します。

Selecting Alternate Provider Configurations

Resources / provider: Selecting a Non-default Provider Configuration

resource "mysql_database" "app1" {
  name = "my_database"
}

resource "mysql_database" "app2" {
  provider = mysql.secondary
  name     = "other_database"
}

ひとつ目のmysql_databaseリソースはDefault Providerを使用し、もうひとつは代替のProviderを使用します。

代替のProviderを指定しているのは、この箇所ですね。

  provider = mysql.secondary

これでterraform applyすると

$ terraform apply

172.17.0.2の方はmy_databaseが作成され、

mysql> show databases where `Database` like '%database%';
+-------------+
| Database    |
+-------------+
| my_database |
+-------------+
1 row in set (0.00 sec)

172.17.0.3の方はother_databaseが作成されます。

mysql> show databases where `Database` like '%database%';
+----------------+
| Database       |
+----------------+
| other_database |
+----------------+
1 row in set (0.00 sec)

OKですね。

では、リソースを破棄しておきます。

$ terraform destroy

モジュールごとにProviderを指定する

次は、モジュールに対してProviderを指定してみましょう。

Modules / Providers Within Modules

モジュールに対しても、リソースと同じように使用するProviderを指定するか、指定しない場合はDefault Providerを使用することに
なります。

ちなみに、Providerの構成を定義するのはルートモジュールとなり、ルートモジュールから呼び出されるモジュールは、
ルートモジュールから構成済みのProviderを受け取るようにするのが良いようです。

Selecting Alternate Provider Configurations

In most cases, only root modules should define provider configurations, with all child modules obtaining their provider configurations from their parents.

というわけで、先にモジュールを作成します。
modules/database/main.tf

variable "name" {
  type = string
}

resource "mysql_database" "app" {
  name = var.name
}

modules/database/versions.tf

terraform {
  required_version = ">= 0.13.4"

  required_providers {

    mysql = {
      source  = "terraform-providers/mysql"
      version = ">= 1.9.0"
    }
  }
}

ルートモジュール側。
main.tf

terraform {
  required_version = "0.13.4"

  required_providers {

    mysql = {
      source  = "terraform-providers/mysql"
      version = "1.9.0"
    }
  }
}

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

module "database1" {
  source = "./modules/database"

  name = "my_database"
}

module "database2" {
  source = "./modules/database"

  providers = {
    mysql = mysql.secondary
  }

  name = "other_database"
}

Providerの構成は、リソースの時の例と同じです。

provider "mysql" {
  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

モジュールへの適用は、なにも指定しないとそのモジュールはDefault Providerを使い、明示的にProviderを指定した場合は
そのProviderを使います。

module "database1" {
  source = "./modules/database"

  name = "my_database"
}

module "database2" {
  source = "./modules/database"

  providers = {
    mysql = mysql.secondary
  }

  name = "other_database"
}

今回はdatabase2でProviderを指定しているわけですが、モジュールの場合はprovidersブロックで使用するProviderを
指定します。

  providers = {
    mysql = mysql.secondary
  }

この状態で、実行。

$ terraform apply

結果は、先ほどのリソースでの例と同じです。

## 172.17.0.2
mysql> show databases where `Database` like '%database%';
+-------------+
| Database    |
+-------------+
| my_database |
+-------------+
1 row in set (0.00 sec)


## 172.17.0.3
mysql> show databases where `Database` like '%database%';
+----------------+
| Database       |
+----------------+
| other_database |
+----------------+
1 row in set (0.00 sec)

実質的な定義は同じなので、そりゃあそうだという感じですね。

では、リソースを破棄しておきます。

$ terraform destroy

ひとつのモジュールに、複数のProviderを指定する

先ほどは、モジュールそれぞれにProviderを明示的に、もしくは暗黙的に指定しました。

今度は、ひとつのモジュールに対して複数のProviderをまとめて指定してみます。

この場合、モジュール自体がいくつProviderを要求するかを宣言することになります。 modules/database/main.tf

variable "name" {
  type = string
}

provider "mysql" {
  alias = "one"
}

provider "mysql" {
  alias = "two"
}

resource "mysql_database" "app1" {
  provider = mysql.one

  name = var.name
}

resource "mysql_database" "app2" {
  provider = mysql.two

  name = var.name
}

今回のモジュールでは2つのProviderを宣言し

provider "mysql" {
  alias = "one"
}

provider "mysql" {
  alias = "two"
}

リソースにそれぞれ適用します。

resource "mysql_database" "app1" {
  provider = mysql.one

  name = var.name
}

resource "mysql_database" "app2" {
  provider = mysql.two

  name = var.name
}

バージョン指定。
modules/database/versions.tf

terraform {
  required_version = ">= 0.13.4"

  required_providers {

    mysql = {
      source  = "terraform-providers/mysql"
      version = ">= 1.9.0"
    }
  }
}

ルートモジュール側。
main.tf

terraform {
  required_version = "0.13.4"

  required_providers {

    mysql = {
      source  = "terraform-providers/mysql"
      version = "1.9.0"
    }
  }
}

provider "mysql" {
  # alias = "default"

  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

module "database" {
  source = "./modules/database"

  providers = {
    mysql.one = mysql
    # mysql.one = mysql.default
    mysql.two = mysql.secondary
  }

  name = "my_database"
}

Defaut Providerと、もうひとつProviderを定義し

provider "mysql" {
  # alias = "default"

  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

provider "mysql" {
  alias = "secondary"

  endpoint = "172.17.0.3:3306"
  username = "root"
  password = "password"
}

モジュールに、複数のProviderを指定します。なので、providersなのでしょうね。

module "database" {
  source = "./modules/database"

  providers = {
    mysql.one = mysql
    # mysql.one = mysql.default
    mysql.two = mysql.secondary
  }

  name = "my_database"
}

#コメントアウトしていますが、Default Providerではなくaliasを指定し

provider "mysql" {
  alias = "default"

  endpoint = "172.17.0.2:3306"
  username = "root"
  password = "password"
}

providersブロックでaliasを指定してもOKです。

  providers = {
    mysql.one = mysql.default
    mysql.two = mysql.secondary
  }

これでTerraformを実行すると

$ terraform apply

172.17.0.2、172.17.0.3の両方のMySQLサーバーに同じ名前のデータベースができます。

mysql> show databases where `Database` like '%database%';
+-------------+
| Database    |
+-------------+
| my_database |
+-------------+
1 row in set (0.00 sec)

ひとつのモジュールに対してProviderは複数指定していても

resource "mysql_database" "app1" {
  provider = mysql.one

  name = var.name
}

resource "mysql_database" "app2" {
  provider = mysql.two

  name = var.name
}

モジュールの中のリソース定義はそれぞれProviderを指定しつつ、同じname変数を使っているのでこうなります。

resource "mysql_database" "app1" {
  provider = mysql.one

  name = var.name
}

resource "mysql_database" "app2" {
  provider = mysql.two

  name = var.name
}

これを変えようとすると、Providerに応じたnameに相当する変数を用意しないといけないわけで。

また、モジュールがいくつProviderを受け取るかも決まっているので、この点でも自由度があるわけではないですね。

任意の数のProviderを受け取って、その分だけ同じリソースを作る、といった使い方はできません。

たとえばモジュール側の定義をこのようにして

variable "name" {
  type = string
}

provider "mysql" {
  alias = "one"
}

provider "mysql" {
  alias = "two"
}

resource "mysql_database" "app" {
  name = var.name
}

ルートモジュール側でこのように呼び出しても、この場合に使われるのはDefault Providerです(alias = oneの方が使われました)。

module "database" {
  source = "./modules/database"

  providers = {
    mysql.one = mysql
    mysql.two = mysql.secondary
  }

  name = "my_database"
}

なので、こういう使い方をしても、Provider分だけ作成できるリソースが繰り返しのように増えるわけではなく、あくまで
どのProviderでどのリソースを作るかは事前に決めておく必要があります、と。

あとはInput Variablesでのコントロールですね。

一応、任意の数だけProviderを取れるようにできることを希望するようなIssueはありますが…どうなるでしょう?

Ability to pass providers to modules in for_each · Issue #24476 · hashicorp/terraform · GitHub

まあ、だいたい雰囲気はわかったので、今回はこれでOKとしましょう。