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
を実行すると、以下のように認識されていることが確認できます。
検証は 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
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 が別なので同じ名前で作れます。
作成した 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 上に作成されています。
リソース毎に明示的に 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 に追加すれば以下のように操作できるようになります。
しかし 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 を使うけど