Azureを使ったハンズオンやワークショップを開催する際、参加者のユーザーや参加者毎の専用リソースグループを用意し、それぞれに適切な権限を設定したいことがあると思います。
本記事では、Terraformのfor eachを使用してこれらの準備を効率的に行う方法について解説します。
for eachとは
Terraformのfor eachは、リソースを複数回繰り返し作成するための機能です。
通常、複数のリソースを作成する際はそれぞれ個別にresourceブロックを記述必要がありますが、この機能を使用すると複数のリソースを1つのresourceブロックで定義することができます。
メリット
1. 効率性の向上
for eachを使用しない場合、コンソールで作成またはresourceブロックを記述する作業をリソースの数だけ繰り返すことになります。
for eachを使用すれば、リストとresourceブロックをそれぞれ1つ記述するだけで作成できます。
2. パラメーターの設定ミスの低減
「同じパラメーターでリソース名だけが違う」というように、一部のパラメーターだけが異なるリソースを複数作成するケースがあると思います。
for eachを使用せずに個別に作成すると、作業ミスで一部のリソースだけ異なる設定にしてしまうことがあるかもしれません。
しかし、for eachを使用すれば、変えたいパラメーター(リソース名等)だけをリストに記述し、他のパラメーターは全く同じリソースを安全に作成することができます。
使い方
まずは、リストを記述します。
locals {
names = ["group_a", "group_b", "group_c"]
}
for eachにこのリストを指定し、{パラメーター名} = each.key と記述すると、そのパラメーターの値がリストの各要素の値となったリソースが作成されます。
下記のコードの場合、リソース名がgroup_a、group_b、group_cのリソースグループ3つが作成されます。
resource "azurerm_resource_group" "example_groups" {
for_each = toset(local.names)
name = each.key
・・・
}
また、for eachを使用して作成されたリソースはマップ型になっているため、それらのパラメーターはeach.value.{パラメーター名}で参照できます。
下記のコードの場合、リソース名がexample_vmの仮想マシンがgroup_a、group_b、group_cに作成されます。
resource "azurerm_windows_virtual_machine" "example_VMs" {
for_each = azurerm_resource_group.example_groups
name = "example_vm"
resource_group_name = each.value.name
・・・
}
想定ユースケース
ここからはfor eachを活用した実践的な例として、Azureのワークショップやハンズオンの環境準備を想定したTerraformコードについて解説します。
想定するユースケースは以下の通りです。
- 参加者にはそれぞれユーザーを発行する
- 参加者毎に専用のリソースグループを作成し、自分のリソースグループでのみリソースの作成/閲覧/更新/削除ができるようにする
- 参加者全員が参照する共通のリソースは閲覧のみ可能にする
構成イメージ
Terraformコード
最終的なコードは以下の通りです。以降の章で各部の解説を行います。
# ユーザー名のリスト
variable "user_names" {
default = ["user1", "user2", "user3"]
}
# ユーザーIDと専用リソースグループの名前のリスト
locals {
user_ids = [for user_name in var.user_names: "${user_name}_workshop@xxxxx.onmicrosoft.com"]
resource_groups = [for user_name in var.user_names: "${user_name}_workshop_group"]
}
# ユーザーを作成
resource "azuread_user" "workshop_users" {
for_each = toset(local.user_ids)
user_principal_name = each.key
display_name = each.key
password = "xxxxx"
}
# グループを作成
resource "azuread_group" "workshop_group" {
display_name = "workshop"
security_enabled = true
}
# ユーザーをグループに追加
resource "azuread_group_member" "workshop_members" {
for_each = azuread_user.workshop_users
group_object_id = azuread_group.workshop_group.object_id
member_object_id = each.value.object_id
}
# 共通リソースグループを作成
resource "azurerm_resource_group" "shared_rg" {
name = "shared_workshop_group"
location = "japaneast"
}
# 専用リソースグループを作成
resource "azurerm_resource_group" "user_rg" {
for_each = toset(local.resource_groups)
name = each.key
location = "japaneast"
}
# 共通リソースグループに対する権限を定義
resource "azurerm_role_definition" "shared_rg_read" {
name = "SharedRGRead"
scope = azurerm_resource_group.shared_rg.id
description = "全員共通RGの閲覧権限"
permissions {
actions = [
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Resources/subscriptions/resourceGroups/resources/read"
]
not_actions = []
}
assignable_scopes = [azurerm_resource_group.shared_rg.id]
}
# 共通リソースグループに権限を付与
resource "azurerm_role_assignment" "group_shared_rg_read_assign" {
scope = azurerm_resource_group.shared_rg.id
role_definition_id = azurerm_role_definition.shared_rg_read.role_definition_resource_id
principal_id = azuread_group.workshop_group.object_id
}
# 専用リソースグループへの権限を定義
resource "azurerm_role_definition" "user_rg_crud" {
for_each = azurerm_resource_group.user_rg
name = "UserRGCRUD_${each.key}"
scope = each.value.id
description = "${each.key}専用RG用のCRUD権限"
permissions {
actions = [
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Resources/subscriptions/resourceGroups/write",
"Microsoft.Resources/subscriptions/resourceGroups/delete",
"Microsoft.Resources/*/read",
"Microsoft.Resources/*/write",
"Microsoft.Resources/*/delete",
"Microsoft.Resources/deployments/*"
]
not_actions = []
}
assignable_scopes = [each.value.id]
}
# 専用リソースグループに権限を付与
resource "azurerm_role_assignment" "user_rg_crud_assign" {
for_each = {
for idx, rg_name in local.resource_groups :
rg_name => local.user_ids[idx]
}
scope = azurerm_resource_group.user_rg[each.key].id
role_definition_id = azurerm_role_definition.user_rg_crud[each.key].role_definition_resource_id
principal_id = azuread_user.workshop_users[each.value].object_id
}
1. ユーザー名のリストを作成
ユーザー名のリストを作成しています。
# ユーザー名のリスト
variable "user_names" {
default = ["user1", "user2", "user3"]
}
2. ユーザーIDと専用リソースグループの名前のリストを作成
1のリストを使用して、ユーザーIDと専用リソースグループの名前のリストを作成します。
# ユーザーIDと専用リソースグループの名前のリスト
locals {
user_ids = [for user_name in var.user_names: "${user_name}_workshop@xxxxx.onmicrosoft.com"]
resource_groups = [for user_name in var.user_names: "${user_name}_workshop_group"]
}
3. ユーザーを作成
2のユーザーIDのリストを使用して、ユーザーを作成します。
※ 手順の簡略化のため、ユーザーの表示名はユーザーIDと同じ値にしています。
# ユーザーを作成
resource "azuread_user" "workshop_users" {
for_each = toset(local.user_ids)
user_principal_name = each.key
display_name = each.key
password = "xxxxx"
}
4. グループを作成 & ユーザーを追加
グループを作成し、3のユーザーをグループに追加します。
# グループを作成
resource "azuread_group" "workshop_group" {
display_name = "workshop"
security_enabled = true
}
# ユーザーをグループに追加
resource "azuread_group_member" "workshop_members" {
for_each = azuread_user.workshop_users
group_object_id = azuread_group.workshop_group.object_id
member_object_id = each.value.object_id
}
5. 共通リソースグループを作成
共通リソースグループを作成します。
# 共通リソースグループを作成
resource "azurerm_resource_group" "shared_rg" {
name = "shared_workshop_group"
location = "japaneast"
}
6. 各ユーザーの専用リソースグループを作成
2の専用リソースグループの名前のリストを使用して、専用リソースグループを作成します。
# 専用リソースグループを作成
resource "azurerm_resource_group" "user_rg" {
for_each = toset(local.resource_groups)
name = each.key
location = "japaneast"
}
7. 共通リソースグループへのRead権限を定義 & グループに権限を付与
5の共通リソースグループへのRead権限を定義し、4のグループに付与します。
# 共通リソースグループに対する権限を定義
resource "azurerm_role_definition" "shared_rg_read" {
name = "SharedRGRead"
scope = azurerm_resource_group.shared_rg.id
description = "全員共通RGの閲覧権限"
permissions {
actions = [
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Resources/subscriptions/resourceGroups/resources/read"
]
not_actions = []
}
assignable_scopes = [azurerm_resource_group.shared_rg.id]
}
# 共通リソースグループに権限を付与
resource "azurerm_role_assignment" "group_shared_rg_read_assign" {
scope = azurerm_resource_group.shared_rg.id
role_definition_id = azurerm_role_definition.shared_rg_read.role_definition_resource_id
principal_id = azuread_group.workshop_group.object_id
}
8. 専用リソースグループへのCRUD権限を定義 & 各ユーザーに権限を付与
6の専用リソースグループへのCRUD権限を定義し、各ユーザーに付与します。
Microsoft.Resources/*/read, write, deleteの、「*」の部分にサービス名を記述することで、特定のサービスに限定した権限を付与することもできます。
# 専用リソースグループへの権限を定義
resource "azurerm_role_definition" "user_rg_crud" {
for_each = azurerm_resource_group.user_rg
name = "UserRGCRUD_${each.key}"
scope = each.value.id
description = "${each.key}専用RG用のCRUD権限"
permissions {
actions = [
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Resources/subscriptions/resourceGroups/write",
"Microsoft.Resources/subscriptions/resourceGroups/delete",
"Microsoft.Resources/*/read",
"Microsoft.Resources/*/write",
"Microsoft.Resources/*/delete",
"Microsoft.Resources/deployments/*"
]
not_actions = []
}
assignable_scopes = [each.value.id]
}
# 専用リソースグループに権限を付与
resource "azurerm_role_assignment" "user_rg_crud_assign" {
for_each = {
for idx, rg_name in local.resource_groups :
rg_name => local.user_ids[idx]
}
scope = azurerm_resource_group.user_rg[each.key].id
role_definition_id = azurerm_role_definition.user_rg_crud[each.key].role_definition_resource_id
principal_id = azuread_user.workshop_users[each.value].object_id
}
確認
terraform applyを実行してAzure環境にコードで記述した内容を反映した後、管理者権限を持つユーザーでログインすると、以下のようにグループにユーザーが3つ追加されていることがわかります。
また、リソースグループも共通1つと専用3つの計4つが作成されていることが確認できます。
続いて、参加者用ユーザーでログインします。(下の画像はuser1_workshop@xxxxx.onmicrosoft.comでログイン)
リソースグループを見に行くと、共通リソースグループと自分の専用リソースグループしか表示されないことが確認できます。
まとめ
以上の手順で効率的にユーザー/グループ/リソースグループの作成と権限の付与ができます。
TerraformをはじめとするIaCツールを使用せずに手作業で上記の環境準備を実施する場合、特に各自の専用リソースグループに対する権限付与の部分で多くの手間がかかってしまいます。
本記事で紹介したケースに限らず、for_eachを使用することで効率的にインフラ構築ができますの皆様もぜひお試しください!




