Ya he contado en numerosas ocasiones las bondades de trabajar con Azure Resource Manager y poco a poco vamos dejando atrás el modo de trabajo clásico de Azure.

Una situación que resulta bastante frecuente cuando trabajamos con las plantillas JSON es la necesidad de crear múltiples instancias de un mismo recurso. Esto es relativamente usual cuando desplegamos máquinas virtuales que van a formar un clúster o granja junto con un balanceador.

Imaginemos la siguiente interfaz de red:

{
      "apiVersion": "[variables('apiVersion')]",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[concat(variables('vmName-WEBSRV'),'-nic')]",
      "location": "[variables('location')]",
      "dependsOn": [
        "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]",
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName-rdp-http'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Static",
              "privateIPAddress": "10.0.20.50",
              "subnet": {
                "id": "[variables('subnetRef')]"
              },
              "loadBalancerBackendAddressPools": [
                {
                  "id": "[concat(variables('lbID'),'/backendAddressPools/LoadBalancerBackend')]"
                }
              ]
            }
          }
        ],
        "networkSecurityGroup": {
          "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName-rdp-http'))]"
        }
      }
}

Lo primero que se nos puede venir a la cabeza es hacer un copia y pega de la definición, crear otra variable vmName-WEBSRV-nic y ya tenemos nuestra segunda interfaz de red para el cluster. Es un razonamiento muy directo pero con algunas lagunas:

  • ¿Qué ocurre si nuestro clúster o granja en lugar de 2 máquinas lo componen 16? ¿16 copia y pega?
  • ¿También copia y pega de sus 16 definiciones de máquinas virtuales? ¿Y sus 16 extensiones de BGInfo y diagnósticos también?
  • Si cambiamos alguna parte de la definición, debemos ser muy cuidadosos de trasladarlo al resto de instancias o tendremos inconsistencias en nuestro clúster.
  • Nuestro archivo JSON será extenso y con muchos datos repetidos. Difícil de navegar y de poco valor.

Afortunadamente AzureRM ya dispone de mecanismos pensados para estas situaciones y hay forma de instanciar un objeto definido en nuestro JSON tantas veces como necesitemos, ¡incluso podemos dejar a discreción del usuario el hacerlo mediante un parámetro! Por otra parte, ten en cuenta que aunque he tomado como ejemplo una interfaz de red, este sistema aplica a prácticamente cualquier recurso de Azure.

El objeto copy:{}

En cualquiera de las definiciones de objetos de nuestra plantilla JSON podemos incluir un objeto copy para indicar a Azure que deberá ser instanciado múltiples veces. Tiene el siguiente aspecto:

"copy": {
    "name": "nombreObjetoBucle",
    "count": 4
},

Más sencillo no podía ser. Le damos un nombre cualquiera al objeto e indicamos cuantas veces debe instanciarse. Si creamos múltiples copias idénticas de un objeto vamos a encontrarnos conflictos con los nombres. ¿Cómo podemos evitarlo? Utilizando el índice, copyIndex().

Allá donde indiquemos copyIndex(offset), se convertirá en un número entero que indicará la instancia que estamos creando. En el ejemplo anterior vamos a crear 4 copias de un elemento. Cuando Azure esté creando la primera copia, copyIndex() valdrá 0, en la segunda 1, en la tercera 2 y en la cuarta 3. De esta forma tenemos una herramienta clave para desplegar múltiples objetos con una sola definición.

¿Y el offset? Es un parámetro muy útil. Como habrás podido ver, copyIndex() empieza la cuenta desde el número 0. En determinadas ocasiones nos puede interesar que la cuenta empiece desde otro número y es aquí donde se lo indicamos. Si especificamos copyIndex(5), la cuenta empezará desde 5.

¿Entendido? ¡Veamos como queda ahora nuestra interfaz de red preparada para ser instanciada múltiples veces!

{
      "apiVersion": "[variables('apiVersion')]",
      "type": "Microsoft.Network/networkInterfaces",
      "name": "[concat(variables('vmName-WEBSRV'),copyIndex(1),'-nic')]",
      "location": "[variables('location')]",
      "copy": {
        "name": "nicLoop",
        "count": 4
      },
      "dependsOn": [
        "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]",
        "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName-rdp-http'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Static",
              "privateIPAddress": "[concat('10.0.20.',copyIndex(50))]",
              "subnet": {
                "id": "[variables('subnetRef')]"
              },
              "loadBalancerBackendAddressPools": [
                {
                  "id": "[concat(variables('lbID'),'/backendAddressPools/LoadBalancerBackend')]"
                }
              ]
            }
          }
        ],
        "networkSecurityGroup": {
          "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName-rdp-http'))]"
        }
      }
}

El ejemplo crea 4 instancias del adaptador de red definido. Prestando atención las líneas 4 y 20 encontramos la función copyIndex() con distintos desplazamientos. Uno nos sirve para dar nombres distintos a cada interfaz, pero empezando desde uno, mientras que el siguiente es perfecto para asignar direcciones IP estáticas a cada máquina.

Mediante el mismo método puedo crear las máquinas virtuales que van a utilizar estos adaptadores. Al final conseguimos un JSON mucho más limpio, fácil de modificar y menos proclive a errores.

Podéis ampliar sobre el tema en la documentación oficial de Microsoft sobre la implementación de varias instancias en AzureRM.

Happy ARM!