しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Terraform の Multiple Provider を利用して複数の Azure Subscription に対する構築を行う

Azure の Subscription は割と気軽に作れるので、メータリングを分ける目的などで Subscription を複数使っているケースもあると思います。そういった時には Multiple Provider を使うといい感じに解決できます。

公式のサンプルだと AWS のリージョン毎に Provider を定義していますが、Azure の場合は現実的に考えて Subscription Id を使うぐらいだと思います。*1

これまで 1 つしか provider block を書いてこなかったので少し違和感がありますが、AzureRM Provider のバージョンを指定しつつ複数の provider を定義するには以下のように書けば良いです。

alias を指定しない場合はそれがデフォルトになるので、この後メインで構築に使う Subscription Id を設定するようにすると便利です。

provider "azurerm" {
  subscription_id = "00000000-0000-0000-0000-000000000000"
  features {}
}

provider "azurerm" {
  alias           = "sub"
  subscription_id = "00000000-0000-0000-0000-000000000000"
  features {}
}

terraform {
  required_providers {
    azurerm = "=2.5.0"
  }
}

この状態で terraform providers を実行すると、以下のように認識されていることが確認できます。

f:id:shiba-yan:20200409173037p:plain

検証は Azure CLI がインストールされたマシン上で行っています。ログイン済みなので Azure CLI からは Subscription を切り替えるだけで、全てのリソースへの操作が行える状態です。

当然ながら、指定した Subscription への権限が必要なので CI から使う場合には注意しましょう。

Data Source を利用する

今回のように Subscription 間で 1 つの Terraform を使う場合は、圧倒的に Data Source としての参照が多いのではないかと思っているので、先にこっちから試すことにしました。

とりあえず適当に既存の Resource Group を参照して、リソース ID を出力するだけの定義を書きました。違いは provider プロパティで利用する Provider の alias を指定しているところです。

data "azurerm_resource_group" "main" {
  name = "Main-RG"
}

data "azurerm_resource_group" "sub" {
  provider = azurerm.sub
  name     = "Sub-RG"
}

output "resource_group" {
  value = {
    main = data.azurerm_resource_group.main.id
    sub  = data.azurerm_resource_group.sub.id
  }
}

この定義に対して terraform apply を実行すると、以下のように既存リソース ID が出力されました。

もちろんリソースを作るときのパラメータとして参照することも出来るので、App Service の Outbound IP を自動で Firewall に設定といった処理も書けます。*2

f:id:shiba-yan:20200409175641p:plain

Storage Account の接続文字列や SQL Database の Firewall / Service Endpoints といった設定に必要なリソース ID などを、Data Source として取ってくれば安全に使えます。

Resource を作成する

あまり使うことはないかもしれないですが、それぞれの Subscription に対して新しくリソースを作成してみます。基本は Data Source の時と同じで provider を明示的に指定すれば、その Provider に紐づいた Subscription にリソースが作成されます。

resource "azurerm_resource_group" "main" {
  name     = "example"
  location = "Japan East"
}

resource "azurerm_resource_group" "sub" {
  provider = azurerm.sub
  name     = "example"
  location = "Japan West"
}

上の定義を使って terraform apply を実行すると、それぞれの Subscription に Resource Group が作成されています。同じ名前にしたので少しわかりにくいですが、Subscription が別なので同じ名前で作れます。

f:id:shiba-yan:20200409163842p:plain

作成した Resource Group に対して、さらに新しく Storage Account を作成してみます。

この時も重要なのは provider の指定で、Resource Group を明示的に指定しているので良い感じに処理してくれそうな雰囲気はありますが、名前以上の意味はありません。

resource "azurerm_storage_account" "main" {
  name                     = "mainstorageterraform"
  location                 = azurerm_resource_group.main.location
  resource_group_name      = azurerm_resource_group.main.name
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

resource "azurerm_storage_account" "sub" {
  provider                 = azurerm.sub
  name                     = "substorageterraform"
  location                 = azurerm_resource_group.sub.location
  resource_group_name      = azurerm_resource_group.sub.name
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

これも terraform apply を実行すると、それぞれの Subscription 上に作成されています。

f:id:shiba-yan:20200409164349p:plain

リソース毎に明示的に provider を指定する必要があるので、メインとして使う Subscription Id をデフォルトにしておくのが簡単です。

module を使うと内部で使用する provider を指定できるので、もっと上手く解決することも出来ます。

module "sub" {
  source = "./sub"
  providers = {
    azurerm = azurerm.sub
  }
}

これで module の内部で使われる Provider は azurerm.sub になるので、明示的に provider を指定する必要がなくなります。module は後から移行するのが難しめですが、活用していきたいですね。

Azure Pipelines から利用する

ここまでローカルで実行していたので色々考えることが少なくて楽でしたが、Azure Pipelines を使って実行させる場合は Service connection で少し工夫が必要です。Service connection や Terraform Task を使わずに、手動で Terraform CLI を叩く場合は気にしなくても良いです。

Azure Pipelines の Service connection は複数の Subscription を操作することを考慮していないですが、Azure Portal で Subscription の IAM に追加すれば以下のように操作できるようになります。

f:id:shiba-yan:20200409165609p:plain

しかし Backend に Azure Storage を使う設定にしていると、Terraform Task は自動的に Service connection の作成時に指定した Subscription Id を暗黙的に使うので、Backend を置く場所には気を使う必要があります。

Backend の Storage を置く Subscription を指定して Service connection を作り、その後に追加の IAM 設定を行うと失敗しません。ちょっとややこしいですが、手動で Service Principal の管理はしたくないのです。

*1:https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#argument-reference

*2:自分は VNET Integration と Service Endpoints を使うけど