Bicep の反復ループ

この記事では、for 構文を使用してコレクション内の項目を反復処理する方法について説明します。 この機能は、0.3.1 以降でサポートされています。 ループを使用すると、リソース、モジュール、変数、プロパティ、または出力の複数のコピーを定義できます。 ループを使用して、Bicep ファイル内で繰り返し構文を回避し、デプロイ時に作成するコピーの数を動的に設定します。 クイック スタートを実行するには、クイック スタート: 複数のインスタンスの作成に関する記事を参照してください。

ループを使って複数のリソースやモジュールを作成するには、各インスタンスが name プロパティに一意の値を持つ必要があります。 配列やコレクションのインデックス値や一意な値を使って、名前を作成できます。

トレーニング リソース

段階的なガイダンスを通じてループの詳細を学習する場合は、「条件とループを使用して柔軟な Bicep テンプレートを作成する」を参照してください。

Loop 構文

ループは次の方法で宣言できます。

  • 整数インデックスを使用します。 このオプションは、"この多数のインスタンスを作成します。" というシナリオがある場合に機能します。range 関数は、開始インデックスから始まり、指定された要素の数を含む整数の配列を作成します。 ループ内では、整数インデックスを使用して値を変更できます。 詳細については、「整数インデックス」を参照してください。

    [for <index> in range(<startIndex>, <numberOfElements>): {
      ...
    }]
    
  • 配列内の項目を使用します。 このオプションは、"配列の各要素に対してインスタンスを作成します。" というシナリオがある場合に機能します。ループ内では、現在の配列要素の値を使用して値を変更できます。 詳細については、「配列要素」を参照してください。

    [for <item> in <collection>: {
      ...
    }]
    
  • 辞書オブジェクト内の項目を使用します。 このオプションは、"オブジェクトの各項目に対してインスタンスを作成します。" というシナリオがある場合に機能します。items 関数は、オブジェクトを配列に変換します。 ループ内では、オブジェクトのプロパティを使用して値を作成できます。 詳細については、「辞書オブジェクト」を参照してください。

    [for <item> in items(<object>): {
      ...
    }]
    
  • 配列内の整数インデックスと項目を使用します。 このオプションは、"配列内の各要素に対してインスタンスを作成する必要がありますが、別の値を作成するには現在のインデックスも必要です。" というシナリオがある場合に機能します。詳細については、「ループ配列とインデックス」を参照してください。

    [for (<item>, <index>) in <collection>: {
      ...
    }]
    
  • 条件付きデプロイを追加します。 このオプションは、"複数のインスタンスを作成する必要がありますが、条件が true の場合にのみデプロイするインスタンスごとに" というシナリオがある場合に機能します。詳細については、「条件判定を伴うループ」を参照してください。

    [for <item> in <collection>: if(<condition>) {
      ...
    }]
    

ループの制限

Bicep でのループの使用には、次の制限があります。

  • Bicep ループは、デプロイの開始時に決定できる値でのみ機能します。
  • ループの反復処理に、負の数を指定したり、800 回を超える数を指定したりすることはできません。
  • 入れ子になった子リソースを持つリソースをループすることはできません。 子リソースをトップレベル リソースに変更します。 「子リソースの反復処理」を参照してください。
  • 複数のレベルのプロパティでループするには、ラムダ式の map 関数を使用します。

整数インデックス

インデックスを使用する簡単な例については、文字列の配列を含む変数を作成します。

param itemCount int = 5

var stringArray = [for i in range(0, itemCount): 'item${(i + 1)}']

output arrayResult array = stringArray

出力には、次の値を含む配列が返されます。

[
  "item1",
  "item2",
  "item3",
  "item4",
  "item5"
]

次の例では、storageCount パラメーターで指定されているストレージ アカウントの数を作成します。 各ストレージ アカウントの 3 つのプロパティが返されます。

param location string = resourceGroup().location
param storageCount int = 2

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, storageCount): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

output storageInfo array = [for i in range(0, storageCount): {
  id: storageAcct[i].id
  blobEndpoint: storageAcct[i].properties.primaryEndpoints.blob
  status: storageAcct[i].properties.statusOfPrimary
}]

ストレージ アカウント リソース名の作成にインデックス i が使用されていることに注意してください。

次の例では、モジュールを複数回配置します。

param location string = resourceGroup().location
param storageCount int = 2

var baseName = 'store${uniqueString(resourceGroup().id)}'

module stgModule './storageAccount.bicep' = [for i in range(0, storageCount): {
  name: '${i}deploy${baseName}'
  params: {
    storageName: '${i}${baseName}'
    location: location
  }
}]

output storageAccountEndpoints array = [for i in range(0, storageCount): {
  endpoint: stgModule[i].outputs.storageEndpoint
}]

配列要素

次の例では、storageNames パラメーターで指定された名前ごとに 1 つのストレージ アカウントを作成します。 各リソース インスタンスの name プロパティは一意である必要があることに注意してください。

param location string = resourceGroup().location
param storageNames array = [
  'contoso'
  'fabrikam'
  'coho'
]

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for name in storageNames: {
  name: '${name}${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

次の例では、配列に対して反復処理を行い、プロパティを定義します。 仮想ネットワーク内に 2 つのサブネットを作成します。 サブネット名は一意である必要があることに注意してください。

param rgLocation string = resourceGroup().location

var subnets = [
  {
    name: 'api'
    subnetPrefix: '10.144.0.0/24'
  }
  {
    name: 'worker'
    subnetPrefix: '10.144.1.0/24'
  }
]

resource vnet 'Microsoft.Network/virtualNetworks@2020-07-01' = {
  name: 'vnet'
  location: rgLocation
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.144.0.0/20'
      ]
    }
    subnets: [for subnet in subnets: {
      name: subnet.name
      properties: {
        addressPrefix: subnet.subnetPrefix
      }
    }]
  }
}

配列とインデックス

次の例では、ストレージ アカウントを定義するときに、配列要素とインデックス値の両方を使用します。

param storageAccountNamePrefix string

var storageConfigurations = [
  {
    suffix: 'local'
    sku: 'Standard_LRS'
  }
  {
    suffix: 'geo'
    sku: 'Standard_GRS'
  }
]

resource storageAccountResources 'Microsoft.Storage/storageAccounts@2022-09-01' = [for (config, i) in storageConfigurations: {
  name: '${storageAccountNamePrefix}${config.suffix}${i}'
  location: resourceGroup().location
  sku: {
    name: config.sku
  }
  kind: 'StorageV2'
}]

次の例では、配列とインデックスの両方の要素を使用して、新しいリソースに関する情報を出力します。

param location string = resourceGroup().location
param orgNames array = [
  'Contoso'
  'Fabrikam'
  'Coho'
]

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = [for name in orgNames: {
  name: 'nsg-${name}'
  location: location
}]

output deployedNSGs array = [for (name, i) in orgNames: {
  orgName: name
  nsgName: nsg[i].name
  resourceId: nsg[i].id
}]

辞書オブジェクト

辞書オブジェクト内の要素を反復処理するには、items 関数を使用します。これにより、オブジェクトが配列に変換されます。 value プロパティを使用して、オブジェクトのプロパティを取得します。 nsg リソース名は一意である必要があることに注意してください。

param nsgValues object = {
  nsg1: {
    name: 'nsg-westus1'
    location: 'westus'
  }
  nsg2: {
    name: 'nsg-east1'
    location: 'eastus'
  }
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = [for nsg in items(nsgValues): {
  name: nsg.value.name
  location: nsg.value.location
}]

条件判定を伴うループ

リソースとモジュールの場合は、ループ構文を含む if 式を追加して、条件に応じてコレクションを展開できます。

次の例では、ループを条件ステートメントと組み合わせて示します。 この例では、1 つの条件がモジュールのすべてのインスタンスに適用されます。

param location string = resourceGroup().location
param storageCount int = 2
param createNewStorage bool = true

var baseName = 'store${uniqueString(resourceGroup().id)}'

module stgModule './storageAccount.bicep' = [for i in range(0, storageCount): if(createNewStorage) {
  name: '${i}deploy${baseName}'
  params: {
    storageName: '${i}${baseName}'
    location: location
  }
}]

次の例は、配列内の現在の要素に固有の条件を適用する方法を示しています。

resource parentResources 'Microsoft.Example/examples@2020-06-06' = [for parent in parents: if(parent.enabled) {
  name: parent.name
  properties: {
    children: [for child in parent.children: {
      name: child.name
      setting: child.settingValue
    }]
  }
}]

バッチ処理でのデプロイ

既定では、Azure リソースは並列でデプロイされます。 ループを使用して、あるリソースの種類のインスタンスを複数作成すると、それらのインスタンスはすべて同時にデプロイされます。 それらが作成される順序は保証されません。 Bicep ファイルの合計リソース数の上限 (800 個) 以外、並列にデプロイされるリソースの数に制限はありません。

あるリソースの種類のすべてのインスタンスを同時に更新したくない場合があるかもしれません。 たとえば、運用環境を更新するとき、一度に特定の数だけ更新されるように更新時間をずらす必要がある場合があります。 その場合、インスタンスのサブセットをまとめてバッチ処理し、同時にデプロイされるように指定できます。 他のインスタンスは、そのバッチが完了するまで待機します。

リソースのインスタンスを順次デプロイするには、batchSize デコレータを追加します。 値には、同時にデプロイするインスタンスの数を設定します。 ループ内の前のインスタンスへの依存関係が作成されるので、前のバッチが完了するまで次のバッチは実行されません。

param location string = resourceGroup().location

@batchSize(2)
resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, 4): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

順次デプロイを行う場合は、バッチ サイズを 1 に設定します。

batchSize デコレーターは、sys 名前空間にあります。 このデコレーターを同じ名前の別の項目と区別する必要がある場合は、デコレータの前に「sys: @sys.batchSize(2)」を付けます。

子リソースの反復処理

入れ子にされた子リソースにループを使用することはできません。 子リソースのインスタンスを複数作成するには、子リソースを最上位レベルのリソースに変更します。

たとえば、通常はファイル サービスとファイル共有を、ストレージ アカウントの入れ子になったリソースとして定義しているとします。

resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'examplestorage'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
  resource service 'fileServices' = {
    name: 'default'
    resource share 'shares' = {
      name: 'exampleshare'
    }
  }
}

複数のファイル共有を作成するには、ファイル共有をストレージ アカウントの外に移動します。 親リソースとの関係は、parent プロパティを通じて定義します。

次の例では、ストレージ アカウント、ファイル サービス、および複数のファイル共有を作成する方法を示しています。

resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' = {
  name: 'examplestorage'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

resource service 'Microsoft.Storage/storageAccounts/fileServices@2021-06-01' = {
  name: 'default'
  parent: stg
}

resource share 'Microsoft.Storage/storageAccounts/fileServices/shares@2021-06-01' = [for i in range(0, 3): {
  name: 'exampleshare${i}'
  parent: service
}]

リソース/モジュール コレクションの参照

ARM テンプレートの references 関数は、リソース コレクションのランタイム状態を表すオブジェクトの配列を返します。 Bicep には明示的な参照関数はありません。 代わりに、シンボリック コレクションが直接使用され、コード生成中に Bicep によって ARM テンプレート参照関数を使用する ARM テンプレートに変換されます。 参照関数を使用してシンボリック コレクションを ARM テンプレートに変換する変換機能には、Bicep CLI バージョン 0.20.X 以降が必要です。 さらに、bicepconfig.json ファイル内で symbolicNameCodegentrue に設定されている必要があります。

整数インデックスの 2 つのサンプルの出力は、次のように書くことができます。

param location string = resourceGroup().location
param storageCount int = 2

resource storageAcct 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, storageCount): {
  name: '${i}storage${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'Storage'
}]

output storageInfo array = map(storageAcct, store => {
  blobEndpoint: store.properties.primaryEndpoints
  status: store.properties.statusOfPrimary
})

output storageAccountEndpoints array = map(storageAcct, store => store.properties.primaryEndpoints)

この Bicep ファイルは、references 関数を使用する次の ARM JSON テンプレートにトランスパイルされます。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "languageVersion": "1.10-experimental",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "storageCount": {
      "type": "int",
      "defaultValue": 2
    }
  },
  "resources": {
    "storageAcct": {
      "copy": {
        "name": "storageAcct",
        "count": "[length(range(0, parameters('storageCount')))]"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "[format('{0}storage{1}', range(0, parameters('storageCount'))[copyIndex()], uniqueString(resourceGroup().id))]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage"
    }
  },
  "outputs": {
    "storageInfo": {
      "type": "array",
      "value": "[map(references('storageAcct', 'full'), lambda('store', createObject('blobEndpoint', lambdaVariables('store').properties.primaryEndpoints, 'status', lambdaVariables('store').properties.statusOfPrimary)))]"
    },
    "storageAccountEndpoints": {
      "type": "array",
      "value": "[map(references('storageAcct', 'full'), lambda('store', lambdaVariables('store').properties.primaryEndpoints))]"
    }
  }
}

前述した ARM JSON テンプレートでは、languageVersion1.10-experimental に設定する必要があり、リソース要素は配列ではなくオブジェクトである点にご注意ください。

次のステップ

  • Bicep ファイルの作成の詳細については、ファイル を参照してください。