Diseño e implementación modularesde plantillas de ARM

Sabemos que existen varias formas de crear una plantilla de Azure Resource Manager (ARM). Es relativamente fácil crear una plantilla que aprovisione todos los recursos necesarios en Azure mediante Visual Studio y Visual Studio Code. Una única plantilla de ARM puede incluir todos los recursos necesarios para una solución en Azure. Esta única plantilla de ARM puede ser pequeña e incluir unos pocos recursos o más grande e incluir numerosos recursos.
Aunque la creación de una sola plantilla que incluya todos los recursos es algo bastante tentador, es aconsejable planificar la implementación de la plantilla de ARM dividida antes en diversas plantillas de ARM más pequeñas, para evitar así problemas relacionados con ellas en el futuro.
En este capítulo, analizaremos cómo escribir plantillas de ARM de forma modular para que puedan evolucionar durante un período de tiempo con intervención mínima en términos de cambios y esfuerzo en las fases de prueba e implementación.
Sin embargo, antes de escribir plantillas modulares, es conveniente conocer los problemas que resuelve la escritura modular de plantillas.

En este capítulo, abordaremos los siguientes temas:

  • Problemas de usar una sola plantilla
  • Comprender la implementación anidada y vinculada
  • Plantillas vinculadas
  • Plantillas anidadas
  • Configuraciones de flujo libre
  • Configuraciones conocidas

Ahora, vamos a explorar en detalle los temas mencionados anteriormente, que te ayudarán
a escribir plantillas modulares utilizando las prácticas recomendadas del sector.
Problemas del enfoque de una sola plantilla
Aunque en principio puede parecer que el uso de una sola plantilla de gran tamaño
que incluya todos los recursos no causará problemas, estos pueden surgir en el futuro.
Analicemos los problemas que pueden surgir con el uso de una sola plantilla grande.
Reducción de la flexibilidad para realizar cambios en la plantilla
El uso de una única plantilla grande con todos los recursos dificulta realizar cambios en
ella en el futuro. Con todas las dependencias, parámetros y variables en una sola plantilla,
introducir cambios en ella puede exigir un tiempo considerable, en comparación con
plantillas más pequeñas. Además, puede suceder que el cambio afecte a otras secciones
de la plantilla y esto pase desapercibido, así como que introduzca errores.
Solucionar problemas en plantillas grandes
La solución de problemas en plantillas grandes es difícil. Esto es un hecho. Cuanto mayor
es el número de recursos incluidos en una plantilla, más difícil es solucionar los problemas
de la plantilla. Una plantilla implementa todos los recursos incluidos en ella y encontrar un
error implica tener que implementar la plantilla con bastante frecuencia. Si este es el caso,
la productividad de los desarrolladores se verá reducida, pues tendrán que esperar a que
finalice la implementación de la plantilla.
Además, la implementación de una sola plantilla conlleva más tiempo que la implementación
de plantillas más pequeñas. Los desarrolladores tienen que esperar a que los recursos que
contienen errores se implementen antes de realizar ninguna acción.

Uso abusivo de dependencias
Las dependencias entre recursos también tienden a ser más complejas cuanto más
grande es la plantilla. Resulta relativamente fácil abusar de la característica dependsOn
en las plantillas de ARM debido a la forma en que funcionan. Cada recurso de la plantilla
puede hacer referencia a todos sus recursos anteriores, en lugar de crear un árbol
de dependencias. Las plantillas de ARM no se quejan si un único recurso depende de
todos los demás recursos de la plantilla de ARM, aunque esos otros recursos pueden
tener interdependencias unos con otros. Esto hace que la introducción de cambios en
las plantillas de ARM propicie que se produzcan errores y, en ocasiones, ni siquiera es
posible realizar cambios.
Agilidad reducida
Normalmente, en un proyecto participan varios equipos y cada uno de ellos es propietario
de sus propios recursos en Azure. A estos equipos les resultará difícil trabajar con una sola
plantilla de ARM porque la actualización de esta debe realizarla un único desarrollador.
La actualización de una sola plantilla con varios equipos puede causar conflictos y fusiones
difíciles de resolver. El hecho de disponer de varias plantillas más pequeñas permite que
cada equipo cree su propia parte de la plantilla de ARM.
No reutilizable
Si tienes una única plantilla, eso es todo lo que tienes y el uso de la plantilla implica
implementar todos los recursos. No hay posibilidad, «out of the box», de seleccionar
recursos individuales sin tener que realizar antes algunas maniobras, como agregar
recursos condicionales. Una única plantilla grande pierde la capacidad de reutilización
porque tomas todos los recursos o ninguno.
Ahora que sabemos que una única plantilla grande presenta tantos problemas,
es recomendable crear plantillas modulares para aprovechar las ventajas que
ofrecen, como las siguientes:

  • Varios equipos pueden trabajar en sus plantillas de manera aislada.
  • Las plantillas se pueden reutilizar en diversos proyectos y soluciones.
  • La depuración y la solución de los problemas de las plantillas son tareas fáciles.

Ahora que hemos abordado algunos de los problemas del uso de una sola plantilla grande,
en la siguiente sección tendremos en cuenta el quid de las plantillas modulares y cómo
pueden ayudar a los desarrolladores a realizar implementaciones eficientes.

Descripción del principio de responsabilidad única
El principio de responsabilidad única es uno de los principios fundamentales de los
principios de diseño SOLID. Este principio establece que una clase o un segmento de código
debe ser responsable de una sola función y tener la propiedad de dicha funcionalidad por
completo. El código debe cambiar o evolucionar solo si se produce un cambio funcional
o un error en la funcionalidad actual, pero en ningún otro caso. Este código no debe
cambiar porque se han producido cambios en algún otro componente o código que no
forma parte del componente actual.
La aplicación de este mismo principio a las plantillas de ARM nos ayuda a crear plantillas
cuya única responsabilidad es implementar un único recurso o funcionalidad, en lugar de
implementar todos los recursos y una solución completa.
El uso de este principio te ayudará a crear varias plantillas, cada una de ellas responsable
de un solo recurso o un grupo pequeño de recursos y no de todos los recursos.
Solucionar problemas y depurar más rápido
Cada implementación de plantilla es una actividad distinta en Azure y constituye una
instancia independiente que consta de entradas, salidas y registros. Cuando se implementan
varias plantillas para implementar una solución, cada implementación de plantilla tiene
entradas de registro separadas, acompañadas de las correspondientes descripciones de
entrada y salida. Es mucho más fácil aislar errores y solucionar problemas mediante estos
registros independientes de varias implementaciones que con el registro de una única
plantilla grande.
Plantillas modulares
Cuando una única plantilla grande se descompone en varias plantillas y cada una de estas
plantillas más pequeñas se encarga de sus propios recursos y posee la propiedad exclusiva
de dichos recursos, los mantiene y se hace responsable de ellos, podemos decir que
tenemos plantillas modulares. Cada plantilla dentro de estas plantillas modulares se ajusta
al principio de responsabilidad única.
Antes de aprender a dividir una plantilla grande en varias plantillas reutilizables más
pequeñas, es importante comprender la tecnología que permite la creación de plantillas
más pequeñas y saber cómo componerlas para implementar soluciones completas.

Recursos de implementación
ARM proporciona un recurso para vincular plantillas. Aunque ya hemos visto detenidamente
las plantillas vinculadas, las mencionaremos una vez más para ayudarte a entender cómo la
vinculación de plantillas contribuye a lograr las propiedades de modularidad, composición
y reutilización.
Las plantillas de ARM proporcionan recursos especializados, conocidos como
implementaciones, que están disponibles en el espacio de nombres Microsoft.Resources.
Un recurso de implementación en una plantilla de ARM tiene un aspecto muy similar al del
segmento de código siguiente:
«resources»: [
{
«apiVersion»: «2019-10-01»,
«name»: «linkedTemplate»,
«type»: «Microsoft.Resources/deployments»,
«properties»: {
«mode»: «Incremental»,

}
}
]

Esta plantilla no necesita mayor explicación; las dos configuraciones más importantes
del recurso de plantilla son el tipo y las propiedades. En este caso, el tipo hace referencia
al recurso de implementación, en lugar de a cualquier recurso de Azure específico
(almacenamiento, máquina virtual, etc.), y las propiedades especifican la configuración
de implementación, incluida una implementación de plantilla vinculada o una
implementación de plantilla anidada.
Sin embargo, ¿qué hace el recurso de implementación? La función de un recurso de
implementación consiste en implementar otra plantilla. Esta otra plantilla puede ser una
plantilla externa, en un archivo de plantilla de ARM independiente, o una plantilla anidada.
Esto significa que es posible invocar otras plantillas desde una plantilla, del mismo modo
que una llamada de función.

En las plantillas de ARM puede haber niveles anidados de implementaciones. Lo que esto
significa es que una única plantilla puede llamar a otra plantilla, la plantilla llamada puede
llamar a otra y esto puede sucederse durante cinco niveles de llamadas anidadas:

Ahora que sabemos que las plantillas grandes pueden modularse con recursos independientes
en plantillas separadas, necesitamos vincularlas y reunirlas para implementar recursos en
Azure. Las plantillas vinculadas y anidadas son formas de componer varias plantillas juntas.
Plantillas vinculadas
Las plantillas vinculadas son plantillas que invocan plantillas externas. Las plantillas externas
se almacenan en archivos de plantilla de ARM distintos. A continuación, se muestra un
ejemplo de plantillas vinculadas:
«resources»: [
{
«apiVersion»: «2019-10-01»,
«name»: «linkedTemplate»,
«type»: «Microsoft.Resources/deployments»,
«properties»: {
«mode»: «Incremental»,
«templateLink»: {
«uri»:»https://mystorageaccount.blob.core.windows.net/
AzureTemplates/newStorageAccount.json»,
«contentVersion»:»1.0.0.0″
},
«parametersLink»: {
«uri»:»https://mystorageaccount.blob.core.windows.net/
AzureTemplates/newStorageAccount.parameters.json»,
«contentVersion»:»1.0.0.0″

}
}
}
]

Las propiedades adicionales más importantes de esta plantilla, en comparación con
la plantilla anterior, son templateLink y parametersLink. En este caso, templateLink
hace referencia a la dirección URL real de la ubicación del archivo de plantilla
externa y parametersLink es la dirección URL de la ubicación del archivo parameters
correspondiente. Cabe señalar que la plantilla del autor de la llamada debe tener derechos
de acceso a la ubicación de la plantilla llamada. Por ejemplo, si las plantillas externas se
almacenan en Azure Blob Storage, protegido mediante claves, las claves de firma de acceso
seguro (SAS) apropiadas deben estar disponibles para la plantilla del autor de la llamada
para que esta pueda tener acceso a las plantillas vinculadas.
También es posible proporcionar los parámetros insertados explícitos en lugar del valor de
parametersLink, como se muestra a continuación:
«resources»: [
{
«apiVersion»: «2019-10-01»,
«name»: «linkedTemplate»,
«type»: «Microsoft.Resources/deployments»,
«properties»: {
«mode»: «Incremental»,
«templateLink»: {
«uri»:»https://mystorageaccount.blob.core.windows.net/
AzureTemplates/newStorageAccount.json»,
«contentVersion»:»1.0.0.0″
},
«parameters»: {
«StorageAccountName»:{«value»: «
[parameters(‘StorageAccountName’)]»}
}
}
}
]

Ahora conoces bien las plantillas vinculadas. Un tema estrechamente relacionado son las
plantillas anidadas, de las que hablaremos en detalle en la siguiente sección.

Plantillas anidadas
En comparación con las plantillas vinculadas externas, las plantillas anidadas son una
característica relativamente nueva en las plantillas de ARM.
Las plantillas anidadas no definen recursos en archivos externos. Los recursos se definen
dentro de la propia plantilla del autor de la llamada y en el recurso de implementación,
como se muestra a continuación:
«resources»: [
{
«apiVersion»: «2019-10-01»,
«name»: «nestedTemplate»,
«type»: «Microsoft.Resources/deployments»,
«properties»: {
«mode»: «Incremental»,
«template»: {
«$schema»: «https://schema.management.azure.com/schemas/2015-
01-01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«resources»: [
{
«type»: «Microsoft.Storage/storageAccounts»,
«name»: «[variables(‘storageName’)]»,
«apiVersion»: «2019-04-01»,
«location»: «West US»,
«properties»: {
«accountType»: «Standard_LRS»
}
}
]
}
}
}
]
En este segmento de código, podemos ver que el recurso de la cuenta de almacenamiento
está anidado dentro de la plantilla original como parte del recurso implementaciones.
En lugar de usar los atributos templateLink y parametersLink, se usa una matriz resources
para crear varios recursos como parte de una sola implementación. La ventaja de usar una
implementación anidada es que los recursos incluidos en un elemento principal se pueden
usar para volver a configurarlos con los mismos nombres. Normalmente, cada nombre de
recurso puede usarse una sola vez en una plantilla. Las plantillas anidadas nos permiten
usarlos en la misma plantilla, garantizan que todas las plantillas son autosuficientes, no tienen
que almacenarse por separado y pueden o no estar accesibles para esos archivos externos.

Ahora que ya entendemos la tecnología que está detrás de las plantillas de ARM modulares,
¿qué debemos hacer para dividir una plantilla grande en plantillas más pequeñas?
Hay varias formas de descomponer una plantilla grande en plantillas más pequeñas. Microsoft
recomienda el siguiente patrón para la descomposición de las plantillas de ARM:

Cuando una plantilla grande se descompone en plantillas más pequeñas, siempre hay
una plantilla principal, que es la que se utiliza para implementar la solución. Esta plantilla
principal o maestra invoca internamente otras plantillas anidadas o vinculadas y estas,
a su vez, invocan otras plantillas; por último, se implementan las plantillas que contienen
recursos de Azure.
La plantilla principal puede invocar una plantilla de recursos de configuración conocida,
que a su vez invocará plantillas que incluyen recursos de Azure. La plantilla de recursos
de configuración conocida es específica de un proyecto o solución y no tiene muchos
factores reutilizables asociados a estos. Las plantillas de recursos de miembro son
plantillas reutilizables que invoca la plantilla de recursos de configuración conocida.
Opcionalmente, la plantilla maestra puede invocar plantillas de recursos compartidos
y otras plantillas de recursos, si existen.
Es importante entender las configuraciones conocidas. Las plantillas pueden crearse
como configuraciones conocidas o como configuraciones de flujo libre.

Configuraciones de flujo libre
Las plantillas de ARM pueden crearse como plantillas genéricas en las que la mayoría de los
valores asignados a las variables, si no todos, se obtienen como parámetros. Esto permite
que la persona que usa la plantilla incluya cualquier valor que considere necesario para
implementar recursos en Azure. Por ejemplo, la persona que implementa la plantilla puede
elegir una máquina virtual de cualquier tamaño, cualquier número de máquinas virtuales y
cualquier configuración y red para su almacenamiento. Esto se conoce como configuración
de flujo libre, en la que la mayor parte de la configuración y las plantillas proceden del
usuario en lugar de estar declaradas en la plantilla.
Este tipo de configuración plantea algunos desafíos. El más destacable es que no todas las
configuraciones se admiten en todas las regiones y todos los centros de datos de Azure.
Las plantillas no podrán crear recursos si la creación de dichos recursos en ubicaciones
o regiones específicas no está permitida. Otro problema que plantea la configuración de
flujo libre es que los usuarios pueden proporcionar cualesquiera valores que consideren
necesarios y la plantilla los asigna, lo que aumenta tanto el coste como la superficie de
implementación incluso aunque dichos valores no sean completamente necesarios.
Configuraciones conocidas
Las configuraciones conocidas, por otra parte, son configuraciones predefinidas específicas
para la implementación de un entorno mediante plantillas de ARM. Estas configuraciones
predefinidas se conocen como configuraciones «tallaje de camisetas». De forma parecida
a las camisetas, disponibles en las tallas pequeña, mediana y grande, las plantillas de ARM
se pueden preconfigurar para implementar un entorno pequeño, mediano o grande en
función de las necesidades. Esto significa que los usuarios no pueden determinar un tamaño
personalizado aleatorio para el entorno, sino que deben elegir entre las diversas opciones
disponibles y, a continuación, las plantillas de ARM ejecutadas en tiempo de ejecución se
asegurarán de que se proporciona la configuración del entorno adecuada.
Por lo tanto, el primer paso para crear una plantilla de ARM modular es decidir la
configuración conocida para el entorno.
A continuación, como ejemplo, se muestra la configuración de la implementación de un
centro de datos en Azure:

Ahora que conocemos las configuraciones, podemos crear plantillas de ARM modulares.

Existen dos métodos para escribir plantillas de ARM modulares:

  • Plantillas compuestas: las plantillas compuestas incluyen enlaces a otras plantillas.
    Dos ejemplos de plantillas compuestas son las plantillas maestras y las intermedias.
  • Plantillas de nivel de hoja: las plantillas de nivel de hoja son plantillas que contienen
    un único recurso de Azure.

Las plantillas de ARM se pueden dividir en plantillas modulares en función de los siguientes
criterios:

  • Tecnología
  • Funcionalidad

La forma idónea de decidir el método modular que va a utilizarse para crear una plantilla
de ARM es la siguiente:

  • Definir plantillas de nivel de hoja o recurso que incluyan un solo recurso. En el
    diagrama que figura más adelante, las plantillas situadas en el extremo derecho
    son plantillas de nivel de hoja. En el diagrama, las máquinas virtuales, la red virtual,
    el almacenamiento y los otros elementos de esa misma columna representan
    plantillas de nivel de hoja.
  • Componer plantillas específicas del entorno mediante las plantillas de nivel de hoja.
    Estas plantillas específicas del entorno proporcionan un entorno de Azure, como un
    entorno de SQL Server, un entorno de App Service o un entorno de centro de datos.
    Vamos a explorar en profundidad este tema. Tomemos como ejemplo un entorno
    de Azure SQL. Para crear un entorno de Azure SQL, se necesitan varios recursos.
    Como mínimo, se debe aprovisionar un servidor SQL Server lógico, una base de
    datos SQL y algunos recursos de firewall de SQL. Todos estos recursos se definen
    en plantillas individuales en el nivel de hoja. A continuación, los recursos se pueden
    componer juntos en una única plantilla que tiene la capacidad de crear un entorno
    de Azure SQL. Cualquiera que quiera crear un entorno SQL puede usar esta plantilla
    compuesta. La Figura 16.3 incluye Centro de datos, Mensajería y App Service como
    plantillas específicas del entorno.
  • Crear plantillas con una abstracción superior para componer varias plantillas
    específicas del entorno en soluciones. Estas plantillas se componen de las plantillas
    específicas del entorno que se crearon en el paso anterior. Por ejemplo, para crear
    una solución de inventario de comercio electrónico que necesite un entorno de App
    Service y un entorno SQL, se pueden componer juntas las dos plantillas de entorno
    App Service y SQL Server. En la Figura 16.3 las plantillas Funcional 1 y Funcional 2 se
    componen de plantillas secundarias.
  • Por último, se debe crear una plantilla maestra, que debe estar compuesta de varias
    plantillas, cada una de ellas capaz de implementar una solución.

Los pasos anteriores para crear una plantilla de diseño modular se pueden entender
fácilmente por medio de la Figura 16.3:

Ahora, implementemos una parte de la funcionalidad mostrada en el diagrama anterior.
En esta implementación, proporcionaremos una máquina virtual con una extensión de
script mediante un enfoque modular. La extensión de script personalizado implementa
binarios de Docker y prepara un entorno de contenedores en una máquina virtual de
Windows Server 2016.

Ahora, vamos a crear una solución mediante plantillas de ARM y un enfoque modular.
Como hemos mencionado anteriormente, el primer paso es crear plantillas de recursos
individuales. Estas plantillas de recursos individuales se utilizarán para componer plantillas
adicionales capaces de crear un entorno. Estas plantillas serán necesarias para crear una
máquina virtual. Todas las plantillas de ARM que se muestran aquí están disponibles en
el código que acompaña a este capítulo. Los nombres y el código de estas plantillas son
los siguientes:

  • Storage.json
  • virtualNetwork.json
  • PublicIPAddress.json
  • NIC.json
  • VirtualMachine.json
  • CustomScriptExtension.json

En primer lugar, echemos un vistazo al código de la plantilla Storage.json. Esta plantilla
proporciona una cuenta de almacenamiento, elemento que necesita toda máquina virtual
para almacenar sus archivos de disco de sistema operativo y datos:
{
«$schema»: «https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«storageAccountName»: {
«type»: «string»,
«minLength»: 1
},
«storageType»: {
«type»: «string»,
«minLength»: 1
},

«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(parameters(‘storageAccountName’))]»
}
}
}

A continuación, veamos el código de la plantilla de dirección IP pública. Una máquina
virtual que debe estar accesible a través de Internet necesita tener un recurso de dirección
IP pública asignado a su tarjeta de interfaz de red. Aunque exponer una máquina virtual
a Internet es opcional, es posible que este recurso se utilice para crear una máquina virtual.
El siguiente código está disponible en el archivo PublicIPAddress.json:
{
«$schema»: «https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«publicIPAddressName»: {
«type»: «string»,
«minLength»: 1
},
«publicIPAddressType»: {
«type»: «string»,
«minLength»: 1

}
}
],
«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(parameters(‘publicIPAddressName’))]»
}
}
}

A continuación, veamos el código de la red virtual. Las máquinas virtuales en Azure
necesitan una red virtual para la comunicación. Esta plantilla se usará para crear una
red virtual en Azure con un intervalo de direcciones predefinido y subredes. El siguiente
código está disponible en el archivo virtualNetwork.json:
{
«$schema»: «https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«virtualNetworkName»: {
«type»: «string»,
«minLength»: 1

},
«subnetPrefix»: {
«type»: «string»,
«minLength»: 1
},
«resourceLocation»: {
«type»: «string»,
«minLength»: 1
}

«subnets»: [
{
«name»: «[parameters(‘subnetName’)]»,
«properties»: {
«addressPrefix»: «[parameters(‘subnetPrefix’)]»
}
}
]
}
}
],
«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(parameters(‘virtualNetworkName’))]»
}
}
}

A continuación, echemos un vistazo al código de la tarjeta de interfaz de red. Una máquina
virtual necesita una tarjeta de red virtual para conectarse a una red virtual y aceptar y enviar
solicitudes a y desde Internet. El siguiente código está disponible en el archivo NIC.json:
{
«$schema»: «https://schema.management.azure.com/schemas/2015-01-
01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«nicName»: {
«type»: «string»,
«minLength»: 1
},
«publicIpReference»: {
«type»: «string»,
«minLength»: 1

[resourceId(subscription().subscriptionId,resourceGroup().name, ‘Microsoft.
Network/publicIPAddresses’, parameters(‘publicIpReference’))]»,
«vnetRef»: «[resourceId(subscription().
subscriptionId,resourceGroup().name, ‘Microsoft.Network/virtualNetworks’,
parameters(‘virtualNetworkReference’))]»,
«subnet1Ref»: «[concat(variables(‘vnetRef’),’/subnets/’,
parameters(‘subnetReference’))]»
},

«id»: «[variables(‘subnet1Ref’)]»
}
}
}
]
}
}
],
«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(parameters(‘nicName’))]»
}
}
}

A continuación, veamos el código para crear una máquina virtual. Cada máquina virtual es
un recurso en Azure y cabe mencionar que esta plantilla no incluye ninguna referencia al
almacenamiento, la red, las direcciones IP públicas ni otros recursos creados anteriormente.
Esta referencia y composición tendrá lugar más adelante en esta sección, mediante otra
plantilla. El siguiente código está disponible en el archivo VirtualMachine.json:
{
«$schema»: «https://schema.management.azure.com/schemas/2015-01
01/deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«vmName»: {
«type»: «string»,
«minLength»: 1

},
«imageOffer»: {
«type»: «string»,
«minLength»: 1
},
«windowsOSVersion»: {
«type»: «string»,
«minLength»: 1
},

«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(parameters(‘vmName’))]»
}
}
}

A continuación, veamos el código para crear una extensión de script personalizado. Este
recurso ejecuta un script de PowerShell en una máquina virtual después de aprovisionarse.
Este recurso ofrece la oportunidad de ejecutar tareas posteriores al aprovisionamiento
en máquinas virtuales de Azure. El siguiente código está disponible en el archivo
CustomScriptExtension.json:
{
«$schema»: «http://schema.management.azure.com/schemas/2015-01-01/
deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«VMName»: {
«type»: «string»,
«defaultValue»: «sqldock»,
«metadata»: {

«commandToExecute»: «[concat(‘powershell -ExecutionPolicy
Unrestricted -file docker.ps1′)]»
},
«protectedSettings»: {
}
}
}
],
«outputs»: {
}
}
A continuación, vamos a ver el código de PowerShell de extensión de script personalizado
que prepara el entorno de Docker. Cabe señalar que, en función de si la característica
de contenedores de Windows ya está instalada o no, puede producirse un reinicio de la
máquina virtual al ejecutar el script de PowerShell. El siguiente script instala el paquete
NuGet, el proveedor DockerMsftProvider y el ejecutable de Docker. El archivo docker.ps1
está disponible en el código que acompaña a este capítulo:
#

docker.ps1

#
Install-PackageProvider -Name Nuget -Force -ForceBootstrap -Confirm:$false
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
-Confirm:$false -verboseInstall-Package -Name docker -ProviderName
DockerMsftProvider -Force -ForceBootstrap -Confirm:$false

Todas las plantillas vinculadas que hemos visto anteriormente se deben cargar en un
contenedor de una cuenta de Azure Blob Storage. Es posible aplicar a este contenedor
una política de acceso privado, como vimos en el capítulo anterior; sin embargo, para este
ejemplo concreto, vamos a establecer la política de acceso como container. Esta opción
permite el acceso a estas plantillas vinculadas sin un token SAS.
Por último, centrémonos en la escritura de la plantilla maestra. En la plantilla maestra, todas
las plantillas vinculadas se componen juntas para crear una solución: para implementar una
máquina virtual y ejecutar un script dentro de ella. El mismo enfoque se puede usar para crear
otras soluciones, como proporcionar un centro de datos compuesto por varias máquinas
virtuales interconectadas. El siguiente código está disponible en el archivo Master.json:

{
«$schema»: «https://schema.management.azure.com/schemas/2015-01-01/
deploymentTemplate.json#»,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«storageAccountName»: {
«type»: «string»,
«minLength»: 1

},
«subnetName»: {
«type»: «string»,
«minLength»: 1
},
«subnetPrefix»: {
«type»: «string»,
«minLength»: 1

«windowsOSVersion»: {
«type»: «string»,
«minLength»: 1
},
«vhdStorageName»: {
«type»: «string»,
«minLength»: 1
},
«vhdStorageContainerName»: {
«type»: «string»,
«minLength»: 1
…[concat(‘https://’,parameters(‘storageAccountName’),’armtfiles.blob.
core.windows.net/’,variables(‘containerName’),’/Storage.json’)]»,
«contentVersion»: «1.0.0.0»

},
«parameters»: {
«storageAccountName»: {
«value»: «[parameters(‘storageAccountName’)]»
},
«storageType»: {
«value»: «[parameters(‘storageType’)]»
},
«resourceLocation»: {
«value»: «[resourceGroup().location]»

«outputs»: {
«resourceDetails»: {
«type»: «object»,
«value»: «[reference(‘GetVM’).outputs.resourceDetails.value]»
}
}
}

Las plantillas maestras invocan las plantillas externas y también coordinan las
interdependencias entre ellas.
Las plantillas externas deben estar disponibles en una ubicación conocida, de modo que la
plantilla maestra pueda acceder a ellas e invocarlas. En este ejemplo, las plantillas externas
se almacenan en el contenedor de Azure Blob Storage y esta información se transfirió a la
plantilla de ARM por medio de parámetros.
El acceso a las plantillas externas de Azure Blob Storage se puede proteger mediante la
configuración de políticas de acceso. A continuación, se muestra el comando utilizado para
implementar la plantilla maestra. Puede parecer un comando complejo, pero la mayoría
de los valores se usan como parámetros. Se recomienda cambiar los valores de estos
parámetros antes la ejecución. Las plantillas vinculadas se han cargado en una cuenta de
almacenamiento denominada st02gvwldcxm5suwe e incluida en el contenedor armtemplates.
El grupo de recursos debe crearse, si todavía no existe. El primer comando se utiliza para
crear un nuevo grupo de recursos en la región West Europe:
New-AzResourceGroup -Name «testvmrg» -Location «West Europe» -Verbose

El resto de los valores de los parámetros son necesarios para configurar cada recurso.
El nombre de la cuenta de almacenamiento y el valor dnsNameForPublicIP deben ser
únicos en Azure:
New-AzResourceGroupDeployment -Name «testdeploy1» -ResourceGroupName
testvmrg -Mode Incremental -TemplateFile «C:\chapter 05\Master.json»
-storageAccountName «st02gvwldcxm5suwe» -storageType «Standard_LRS»
-publicIPAddressName «uniipaddname» -publicIPAddressType «Dynamic»
-dnsNameForPublicIP «azureforarchitectsbook» -virtualNetworkName
vnetwork01 -addressPrefix «10.0.1.0/16» -subnetName «subnet01» -subnetPrefix
«10.0.1.0/24» -nicName nic02 -vmSize «Standard_DS1» -adminUsername «sysadmin»
-adminPassword $(ConvertTo-SecureString -String sysadmin@123 -AsPlainText
-Force) -vhdStorageName oddnewuniqueacc -vhdStorageContainerName vhds
-OSDiskName mynewvm -vmName vm10 -windowsOSVersion 2012-R2-Datacenter
-imagePublisher MicrosoftWindowsServer -imageOffer WindowsServer
-containerName armtemplates -Verbose

En esta sección, hemos abordado las prácticas recomendadas para descomponer plantillas
grandes en plantillas reutilizables más pequeñas y combinarlas juntas en el entorno de
ejecución para implementar soluciones completas en Azure. A medida que avancemos por
el libro, modificaremos la plantilla de ARM paso a paso hasta que hayamos explorado sus
partes principales. Utilizamos cmdlets de Azure PowerShell para iniciar la implementación
de plantillas en Azure.
Pasemos al tema de copy y copyIndex.
Descripción de copy y copyIndex
Hay muchas veces en las que se necesitan varias instancias de un recurso determinado
o de un grupo de recursos. Por ejemplo, es posible que necesites aprovisionar 10 máquinas
virtuales del mismo tipo. En tales casos, no es prudente implementar plantillas 10 veces
para crear estas instancias. Un mejor enfoque alternativo es utilizar las características copy
y copyIndex de las plantillas de ARM.
copy es un atributo de cada definición de recurso. Esto significa que se puede utilizar para
crear varias instancias de cualquier tipo de recurso.
Vamos a entenderlo con la ayuda de un ejemplo de creación de varias cuentas de
almacenamiento en una sola implementación de plantilla de ARM.

El siguiente fragmento de código crea 10 cuentas de almacenamiento en serie. Se podrían
haber creado en paralelo usando Parallel en lugar de Serial para la propiedad mode:
«resources»: [
{
«apiVersion»: «2019-06-01»,
«type»: «Microsoft.Storage/storageAccounts»,
«location»: «[resourceGroup().location]»,
«name»: «[concat(variables(‘storageAccountName’), copyIndex())]»,
«tags»:{
«displayName»: «[variables(‘storageAccountName’)]»
},
«sku»:{
«name»:»Premium_ZRS»
},
«kind»: «StorageV2»,
«copy»:{
«name»: «storageInstances»,
«count»: 10,
«mode»: «Serial»
}
}
],

En el código anterior, se ha utilizado copy para aprovisionar 10 instancias de la cuenta
de almacenamiento en serie, es decir, una tras otra. Los nombres de las cuentas de
almacenamiento deben ser únicos para las 10 instancias y copyIndex se ha utilizado para
hacerlos únicos al concatenar el nombre de almacenamiento original con el valor del
índice. El valor devuelto por la función copyIndex cambia en cada iteración; comenzará en
0 y continuará para 10 iteraciones. Esto significa que devolverá 9 para la última iteración.
Ahora que hemos aprendido a crear varias instancias de una plantilla de ARM, vamos
a profundizar en proteger estas plantillas frente a vulnerabilidades conocidas.

Proteger las plantillas de ARM
Otro aspecto importante relacionado con la creación de plantillas de ARM empresariales
es protegerlas adecuadamente. Las plantillas de ARM contienen la configuración de los
recursos y la información esencial sobre la infraestructura, por lo que no deben estar
comprometidas ni ser accesibles para personas no autorizadas.
El primer paso para proteger las plantillas de ARM es almacenarlas en cuentas de
almacenamiento y detener cualquier acceso anónimo al contenedor de la cuenta
de almacenamiento. Además, los tokens SAS se deben generar para las cuentas de
almacenamiento y usarse en plantillas de ARM para consumir plantillas vinculadas.
Esto garantizará que solo los titulares de tokens SAS puedan acceder a las plantillas.
Además, estos tokens SAS deben almacenarse en Azure Key Vault en lugar de codificarse
en plantillas de ARM. Esto garantizará que ni siquiera las personas responsables de la
implementación tengan acceso al token SAS.
Otro paso para proteger las plantillas de ARM es garantizar que ninguna información
confidencial y secreto, como cadenas de conexión de base de datos, identificadores de
suscripción e inquilinos de Azure, identificadores de entidad de servicio, direcciones IP,
entre otros, se pueda codificar en las plantillas de ARM. Todos deben estar parametrizados
y los valores deben capturarse en el entorno de ejecución desde Azure Key Vault. Sin
embargo, antes de usar este enfoque, es importante que estos secretos se almacenen en
Key Vault antes de ejecutar cualquier plantilla de ARM.
El código siguiente muestra una de las formas en que se pueden extraer valores de Azure
Key Vault en el entorno de ejecución mediante el archivo de parámetros:
{
«$schema»: https://schema.management.azure.com/schemas/2016-01-01/
deploymentParameters.json#,
«contentVersion»: «1.0.0.0»,
«parameters»: {
«storageAccountName»: {
«reference»: {
«keyVault»: {
«id»: «/subscriptions/–subscription id –/
resourceGroups/rgname/providers/Microsoft.KeyVault/vaults/keyvaultbook»),
«secretName»: «StorageAccountName»
}
}
}
}
}
En esta lista de código, se define un parámetro que hace referencia a Azure Key Vault para
recuperar valores en el entorno de ejecución durante la implementación. El identificador
de Azure Key Vault y el nombre del secreto se han proporcionado como valores de entrada.

Ahora que has aprendido a proteger las plantillas de ARM, vamos a echar un vistazo
a la identificación de las diversas dependencias entre ellas y a cómo podemos habilitar
la comunicación entre varias plantillas.
Usar salidas entre plantillas de ARM
Uno de los aspectos importantes que se pueden pasar fácilmente por alto al utilizar
plantillas vinculadas es que puede haber dependencias de recursos dentro de plantillas
vinculadas. Por ejemplo, un recurso de SQL Server podría estar en una plantilla vinculada
que sea diferente de la de un recurso de máquina virtual. Si queremos abrir el firewall
de SQL Server para la dirección IP de la máquina virtual, deberíamos poder pasar esta
información dinámicamente al recurso de firewall de SQL Server después de aprovisionar
la máquina virtual.
Esto se podría hacer mediante el método sencillo de hacer referencia al recurso de dirección
IP mediante la función REFERENCES si los recursos de SQL Server y máquina virtual están en la
misma plantilla.
En el caso de las plantillas vinculadas, resulta un poco más complejo compartir valores de
propiedad en el entorno de ejecución entre recursos cuando se encuentran en plantillas
diferentes.
Las plantillas de ARM proporcionan una configuración outputs, que es responsable de
generar las salidas a partir de la implementación de la plantilla actual y devolverlas al
usuario. Por ejemplo, podríamos generar un objeto completo, como se muestra en la
siguiente lista de código, utilizando la función reference, o podríamos simplemente
generar una dirección IP como un valor de cadena:
«outputs»: {
«storageAccountDetails»: {
«type»: «object»,
«value»: «[reference(resourceid
(‘Microsoft.Storage/storageAccounts’,
variables(‘storageAccountName’)))]»,
«virtualMachineIPAddress»: {
«type»: «string»,
«value»: «[reference(variables
(‘publicIPAddressName’)).properties.ipAddress]»
}
}
}

La plantilla maestra puede utilizar los parámetros de una plantilla vinculada. Cuando se
llama a una plantilla vinculada, la salida está disponible para la plantilla maestra que se
puede suministrar como parámetro a la siguiente plantilla vinculada o anidada. De esta
manera, es posible enviar los valores de configuración en el entorno de ejecución de los
recursos de una plantilla a otra.
El código de la plantilla maestra sería similar al que se muestra aquí; este es el código que
se utiliza para llamar a la primera plantilla:
{
«type»: «Microsoft.Resources/deployments»,
«apiVersion»: «2017-05-10»,
«name»: «createvm»,
«resoureceGroup»: «myrg»,
«dependsOn»: [
«allResourceGroups»
],
«properties»:{
«mode»: «Incremental»,
«templateLink»:{
«uri»: «[variables(
‘templateRefSharedServicesTemplateUri’)]»,
«contentVersion»: «1.0.0.0»
},
«parameters»: {
«VMName»: {
«value»: «[variables(‘VmName’)]»
}
}
}

}

El fragmento de código anterior de la plantilla maestra llama a una plantilla anidada
responsable del aprovisionamiento de una máquina virtual. La plantilla anidada tiene una
sección de salida que proporciona la dirección IP de la máquina virtual. La plantilla maestra
tendrá otro recurso de implementación en su plantilla que tomará el valor de salida y lo
enviará como un parámetro a la siguiente plantilla anidada, pasando la dirección IP en el
entorno de ejecución. Esto se muestra en el siguiente código:
{
«type»: «Microsoft,Resources/deployments»,
«apiVersion»: «2017-05-10»,
«name»: «createSQLServer»,
«resourceGroup»: «myrg»,
«dependsOn»: [
«createvm»
],
«properties»: {
«mode»: «Incremental»,
«templateLink»: {
«uri»: «[variables(‘templateRefsql’)]»,
«contentVersion»: «1.0.0.0»
},
«parameters»: {
«VMName»: {
«value»: «[reference
(‘createvm’).outputs.virtualMachineIPAddress.value]»
}
}
}
}

En la lista de código anterior, se invoca una plantilla anidada y se le pasa un parámetro.
El valor del parámetro se deriva de la salida de la plantilla vinculada anterior, que se
denomina virtualMachineIPAddress. Ahora, la plantilla anidada obtendrá la dirección IP
de la máquina virtual dinámicamente y puede usarla como dirección IP en la lista blanca.
Con este enfoque, podemos pasar valores de entorno de ejecución de una plantilla anidada
a otra.

Resumen
Las plantillas de ARM constituyen el método de preferencia para el aprovisionamiento
de recursos en Azure. Son plantillas idempotentes por naturaleza, pues aportan coherencia,
previsibilidad y posibilidad de reutilización al proceso de creación de entornos. En este
capítulo, hemos visto cómo crear una plantilla de ARM modular. Es importante que los
equipos dediquen tiempo de calidad al diseño idóneo de plantillas de ARM, de modo que
varios equipos puedan trabajar en ellas al mismo tiempo. Son altamente reutilizables
y requieren cambios mínimos para evolucionar. En este capítulo, hemos aprendido
a crear plantillas seguras por diseño, aprovisionar varias instancias de recursos en una
sola implementación y pasar salidas de una plantilla anidada a otra mediante la sección
outputs de las plantillas de ARM.
En el siguiente capítulo trataremos un aspecto de la tecnología diferente y muy popular,
conocido como tecnología sin servidor en Azure. Azure Functions es uno de los principales
recursos sin servidor de Azure y, por este motivo, lo exploraremos en profundidad, incluidas
las Durable Functions.