Desplegando y administrando recursos usando Azure Portal, Templates y Powershell

Una de las grandes ventajas de Azure es que no hace falta ser un administrador avanzado para poder desplegar nuestros recursos. El portal de administración, por ejemplo, es bastante intuitivo haciendo de la experiencia un paseo point-and-click en donde las diferentes opciones se van presentando y seleccionamos aquellas que más nos interesen.

Por ejemplo, en este artículo vamos a explorar las capacidades básicas de administración de Azure asociadas con el aprovisionamiento de recursos. Suena un poco a rollo pero no te preocupes, en realidad todo es muy sencillo.

Cómo crear grupos de recursos en Azure

El primer paso pasa por definir un grupo de recursos. Un grupo de recursos es un contenedor que almacena los recursos relacionados con una solución de Azure. El grupo de recursos puede incluir todos los recursos de la solución o solo aquellos que se desean administrar como grupo. Por lo general, se recomienda agregar recursos que compartan el mismo ciclo de vida al mismo grupo de recursos para que los pueda implementar, actualizar y eliminar con facilidad como un grupo.

  • Inicia sesión en Azure Portal.

  • Selecciona Grupos de recursos.

  • Seleccione Agregar (Add).

  • Escribe los siguientes valores:

    • Suscripción: Selecciona tu suscripción a Azure.

    • Grupo de recursos: Escribe un nuevo nombre para el grupo de recursos.

    • Región: Selecciona una ubicación de Azure, como Centro de EE. UU.

    • Selecciona Revisar + crear.

    • Selecciona Crear. El grupo de recursos tarda unos segundos en crearse.

  • Selecciona Actualizar en el menú superior para actualizar la lista de grupos de recursos y, después, selecciona el grupo de recursos recién creado para abrirlo. También puedes seleccionar en la sección de Notificación(el icono de campana) en la parte superior y, después, seleccionar Ir al grupo de recursos para abrir el grupo de recursos recién creado

Y ya lo tenemos. Ahora bien, si te da pereza, no te preocupes. Al crear un recurso, también disponemos de la opción de crear un nuevo grupo de recursos o utilizar uno existente.

Estoy casi seguro que ahora es cuando surge la pregunta tonta (en IT no hay preguntas tontas) - Todo esto es muy bonito pero ¿existe alguna forma sencilla de hacer lo mismo sin salir del terminal de PowerShell o Azure CLI? - Por supuesto:

  • PowerShell: New-AzResourceGroup -Name demoResourceGroup -Location westus

  • Azure CLI: az group create --name demoResourceGroup --location westus

Para enumerar los grupos de recursos existentes podemos usar los siguientes comandos:

  • PowerShell: Get-AzResourceGroup

  • Azure CLI: az group list

Exportación de grupos de recursos a plantillas

Después de configurar el grupo de recursos correctamente, quizás quieras tener una copia en una plantilla de Resource Manager para el grupo de recursos que acabas de crear. Exportar la plantilla ofrece dos ventajas:

  • Automatiza las futuras implementaciones de la solución porque la plantilla contiene la infraestructura completa.

  • Obtenemos información extra sobre la sintaxis de la plantilla consultando la notación de objetos JavaScript (JSON) que representa la solución.

Para exportar todos los recursos de un grupo de recursos, use az group export y proporcione el nombre del grupo de recursos.

  • PowerShell: Export-AzResourceGroup -ResourceGroupName resourceGroupName

  • Azure CLI: az group export --name resourceGroupName

El comando muestra la plantilla en la consola. Tenemos que copiar  la salida y guardarla en un archivo JSON. Luego podemos utilizar esta plantilla para futuras implementaciones.

Una vez creado nuestro grupo de recursos podemos añadir a este los recursos que creamos necesarios para nuestro proyecto. Para no complicarlo mucho lo que haremos a continuación es añadir un disco duro como recurso.

Lo que haremos entonces será dirigirnos al portal de Azure, de ahí usaremos la barra de búsqueda y seleccionaremos Disks.

A continuación haremos click en + Create para crear nuestro nuevo recurso.

Esto puede ser por ejemplo:

Click Review + Create y luego de nuevo click en Create. El proceso no lleva mucho, generalmente algo menos de un minuto.

Por supuesto, también podemos hacer la misma tarea utilizando PowerShell o Azure CLI. De hecho podemos personalizar tanto como queramos. Por ejemplo creando un script:

$diskconfig = New-AzDiskConfig -Location 'Central US' -DiskSizeGB 5 -SkuName Standard_LRS -OsType Windows -CreateOption Empty -EncryptionSettingsEnabled $true;

$secretUrl = https://myvault.vault-int.azure-int.net/secrets/123/;

$secretId = '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/ResourceGroup01/providers/Microsoft.

KeyVault/vaults/TestVault123';

$keyUrl = https://myvault.vault-int.azure-int.net/keys/456;

$keyId = '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/ResourceGroup01/providers/Microsoft.

KeyVault/vaults/TestVault456';

$diskconfig = Set-AzDiskDiskEncryptionKey -Disk $diskconfig -SecretUrl $secretUrl -SourceVaultId $secretId;

$diskconfig = Set-AzDiskKeyEncryptionKey -Disk $diskconfig -KeyUrl $keyUrl -SourceVaultId $keyId;

New-AzDisk -ResourceGroupName 'ResourceGroup01' -DiskName 'Disk01' -Disk $diskconfig;

Donde el primer comando crea un objeto de disco vacío local con un tamaño de 5 GB en el tipo de cuenta de almacenamiento Standard_LRS. También establece el tipo de sistema operativo Windows y habilita la configuración de cifrado. Los comandos segundo y tercero establecen la clave de cifrado de disco y la configuración de la clave de cifrado de clave para el objeto de disco. El último comando toma el objeto de disco y crea un disco con el nombre 'Disk01' en el grupo de recursos 'ResourceGroup01'.

Cómo ves esto de usar PowerShell da mucho juego, ¿verdad?

Administrando recursos de Azure mediante plantillas ARM

Azure Resource Manager (ARM) es el servicio de implementación y administración usado en Azure. Proporciona una capa de administración que nos permite crear, actualizar y eliminar recursos de nuestra cuenta de Azure. Sobra decir que nos permite implementar todo tipo de servicios, aplicaciones y recursos sin despeinarnos, levantando complejos entornos en cuestión de minutos y añadiendo características de administración, como el control de acceso, auditoria, monitorización y organización.

Estructura de archivo de plantilla ARM

Al escribir una plantilla ARM, debemos comprender todas las partes que componen la plantilla y lo que hacen. Los archivos de plantilla ARM se componen de los siguientes elementos:

  • schema. Una sección obligatoria que define la ubicación del archivo de esquema JSON que describe la estructura de los datos JSON. El número de versión que usa depende del alcance de la implementación y su editor JSON.

  • contentVersion. Una sección obligatoria que define la versión de su plantilla (como 1.0.0.0). Puede usar este valor para documentar cambios significativos en su plantilla para asegurarse de que está implementando la plantilla correcta.

  • apiProfile. Una sección opcional que define una colección de versiones de API para tipos de recursos. Puede utilizar este valor para evitar tener que especificar versiones de API para cada recurso en la plantilla.

  • parameters. Una sección opcional donde se definen los valores que se proporcionan durante la implementación. Estos valores pueden proporcionarse mediante un archivo de parámetros, mediante parámetros de línea de comandos o en Azure Portal.

  • variables. Una sección opcional en la que se definen valores que se utilizan para simplificar las expresiones del lenguaje de plantilla.

  • functions. Una sección opcional donde puede definir funciones definidas por el usuario que están disponibles dentro de la plantilla. Las funciones definidas por el usuario pueden simplificar la plantilla cuando se utilizan expresiones complicadas repetidamente en su plantilla.

  • resources. Una sección obligatoria que define los elementos reales para implementar o actualizar en un grupo de recursos o una suscripción.

  • output. Una sección opcional en la que especifica los valores que se devolverán al final de la implementación.

Creación de una máquina virtual

La creación de una máquina virtual de Azure normalmente incluye dos pasos:

Crear un grupo de recursos. Ya lo habíamos comentado antes pero lo menciono de nuevo. Un grupo de recursos de Azure es un contenedor lógico en el que se implementan y se administran los recursos de Azure. Y no, no le des más vueltas, sí o sí debes crear un grupo de recursos antes de crear un recurso como una máquina virtual en Azure.

Muchas plantillas ya se encuentran disponibles por defecto en el repositorio, por ejemplo para este ejemplo basta con apuntar la dirección URI de una de estas plantillas para crear una máquina virtual, desplegar un cluster o definir alertas métricas.

El contenido de estas plantillas no deja de ser un archivo JSON que es muy fácil de entender. Por ejemplo:

{
  "$schema": "https://schema.management.azure.com
/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.4.1.14562",
      "templateHash": "8381960602397537918"
    }
  },
  "parameters": {
    "adminUsername": {
      "type": "string",
      "metadata": {
        "description": "Username for the Virtual Machine."
      }
    },
    "adminPassword": {
      "type": "secureString",
      "minLength": 12,
      "metadata": {
        "description": "Password for the Virtual Machine."
      }
    },
    "dnsLabelPrefix": {
      "type": "string",
      "defaultValue": "[toLower(format('{0}-{1}', 
parameters('vmName'), uniqueString(resourceGroup()
.id, parameters('vmName'))))]",
      "metadata": {
        "description": "Unique DNS Name for the Public IP 
used to access the Virtual Machine."
      }
    },
    "publicIpName": {
      "type": "string",
      "defaultValue": "myPublicIP",
      "metadata": {
        "description": 
"Name for the Public IP used to access the Virtual Machine."
      }
    },
    "publicIPAllocationMethod": {
      "type": "string",
      "defaultValue": "Dynamic",
      "allowedValues": [
        "Dynamic",
        "Static"
      ],
      "metadata": {
        "description": "Allocation method for the Public IP 
used to access the Virtual Machine."
      }
    },
    "publicIpSku": {
      "type": "string",
      "defaultValue": "Basic",
      "allowedValues": [
        "Basic",
        "Standard"
      ],
      "metadata": {
        "description": "SKU for the Public IP 
used to access the Virtual Machine."
      }
    },
    "OSVersion": {
      "type": "string",
      "defaultValue": "2019-Datacenter",
      "allowedValues": [
        "2008-R2-SP1",
        "2012-Datacenter",
        "2012-R2-Datacenter",
        "2016-Nano-Server",
        "2016-Datacenter-with-Containers",
        "2016-Datacenter",
        "2019-Datacenter",
        "2019-Datacenter-Core",
        "2019-Datacenter-Core-smalldisk",
        "2019-Datacenter-Core-with-Containers",
        "2019-Datacenter-Core-with-Containers-smalldisk",
        "2019-Datacenter-smalldisk",
        "2019-Datacenter-with-Containers",
        "2019-Datacenter-with-Containers-smalldisk"
      ],
      "metadata": {
        "description": "The Windows version for the VM. 
This will pick a fully patched image of this given Windows version."
      }
    },
    "vmSize": {
      "type": "string",
      "defaultValue": "Standard_D2_v3",
      "metadata": {
        "description": "Size of the virtual machine."
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for all resources."
 }
    },
    "vmName": {
      "type": "string",
      "defaultValue": "simple-vm",
      "metadata": {
        "description": "Name of the virtual machine."
      }
    }
  },
  "functions": [],
  "variables": {
    "storageAccountName": "[format('bootdiags{0}', 
uniqueString(resourceGroup().id))]",
    "nicName": "myVMNic",
    "addressPrefix": "10.0.0.0/16",
    "subnetName": "Subnet",
    "subnetPrefix": "10.0.0.0/24",
    "virtualNetworkName": "MyVNET",
    "networkSecurityGroupName": "default-NSG"
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-04-01",
      "name": "[variables('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "Storage"
    },
    {
      "type": "Microsoft.Network/publicIPAddresses",
      "apiVersion": "2021-02-01",
      "name": "[parameters('publicIpName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('publicIpSku')]"
      },
      "properties": {
        "publicIPAllocationMethod": 
"[parameters('publicIPAllocationMethod')]",
        "dnsSettings": {
          "domainNameLabel": "[parameters('dnsLabelPrefix')]"
        }
      }
    },
    {
      "type": "Microsoft.Network/networkSecurityGroups",
      "apiVersion": "2021-02-01",
      "name": "[variables('networkSecurityGroupName')]",
      "location": "[parameters('location')]",
      "properties": {
        "securityRules": [
          {
            "name": "default-allow-3389",
            "properties": {
              "priority": 1000,
              "access": "Allow",
              "direction": "Inbound",
              "destinationPortRange": "3389",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2021-02-01",
      "name": "[variables('virtualNetworkName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('addressPrefix')]"
          ]
        }
      }
    },
    {
      "type": "Microsoft.Network/virtualNetworks/subnets",
      "apiVersion": "2021-02-01",
      "name": "[format('{0}/{1}', 
variables('virtualNetworkName'), 
variables('subnetName'))]",
      "properties": {
        "addressPrefix": "[variables('subnetPrefix')]",
        "networkSecurityGroup": {
          "id": "[resourceId('Microsoft.
Network/networkSecurityGroups', 
variables('networkSecurityGroupName'))]"
        }
      },
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks', 
variables('virtualNetworkName'))]"
      ]
    },
    {
"type": "Microsoft.Network/networkInterfaces",
      "apiVersion": "2021-02-01",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.
Network/publicIPAddresses', 
parameters('publicIpName'))]"
              },
              "subnet": {
                "id": "[resourceId('Microsoft.
Network/virtualNetworks/subnets', variables('virtualNetworkName'), 
variables('subnetName'))]"
              }
            }
          }
        ]
      },
      "dependsOn": [
        "[resourceId('Microsoft.Network/publicIPAddresses', 
parameters('publicIpName'))]",
        "[resourceId('Microsoft.Network/virtualNetworks/subnets', 
variables('virtualNetworkName'), variables('subnetName'))]"
      ]
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2021-03-01",
      "name": "[parameters('vmName')]",
      "location": "[parameters('location')]",
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "MicrosoftWindowsServer",
            "offer": "WindowsServer",
            "sku": "[parameters('OSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage",
            "managedDisk": {
              "storageAccountType": "StandardSSD_LRS"
            }
          },
          "dataDisks": [
            {
              "diskSizeGB": 1023,
              "lun": 0,
              "createOption": "Empty"
            }
          ]
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.
Network/networkInterfaces', variables('nicName'))]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
            "enabled": true,
            "storageUri": "[reference(resourceId
('Microsoft.Storage/storageAccounts', 
variables('storageAccountName'))).primaryEndpoints.blob]"
          }
        }
      },
      "dependsOn": [
        "[resourceId('Microsoft.
Network/networkInterfaces', 
variables('nicName'))]",
        "[resourceId('Microsoft.Storage/storageAccounts', 
variables('storageAccountName'))]"
      ]
    }
  ],
  "outputs": {
    "hostname": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.
Network/publicIPAddresses', parameters('publicIpName'))).
dnsSettings.fqdn]"
    }
  }
}

 

Para crear nuestro recurso podemos hacer uso del siguiente script:

$resourceGroupName = Read-Host -Prompt "Enter the Resource Group name"

$location = Read-Host -Prompt "Enter the location (i.e. centralus)"

$adminUsername = Read-Host -Prompt "Enter the administrator username"

$adminPassword = Read-Host -Prompt "Enter the administrator password" -AsSecureString

$dnsLabelPrefix = Read-Host -Prompt "Enter an unique DNS name for the public IP"

New-AzResourceGroup -Name $resourceGroupName -Location "$location"

New-AzResourceGroupDeployment

&-ResourceGroupName $resourceGroupName

-TemplateUri "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/maste..."

-adminUsername $adminUsername

-adminPassword $adminPassword

-dnsLabelPrefix $dnsLabelPrefix

(Get-AzVm -ResourceGroupName $resourceGroupName).name

Ahora bien, si queremos personalizar nuestro despliegue lo mejor es descargar el fichero JSON en nuestro equipo y definir dos archivos, uno para la plantilla en sí y otro para los parámetros. Para ello usaremos los parámetros:

-TemplateParameterFile <String>

-TemplateFile <String>

Por ejemplo, en PowerShell:

$templateFile = "azuredeploy.json"

$today=Get-Date -Format "MM-dd-yyyy"

$deploymentName="blanktemplate-"+"$today"

New-AzResourceGroupDeployment `

-Name $deploymentName

-TemplateFile $templateFile

¿Y si quisiéramos desplegar una nueva cuenta de Storage usando Azure CLI? Pues más o menos lo mismo. Tomamos el fichero JSON que define nuestro recurso y lanzamos el comando a continuación indicando la ruta donde tengamos almacenada la plantilla.

{
   "$schema": "https://schema.management.azure.com
/schemas/2019-04-01/deploymentTemplate.json#",
   "contentVersion": "1.0.0.1",
   "apiProfile": "",
   "parameters": {},
   "variables": {},
   "functions": [],
   "resources": [
      {
          "type": "Microsoft.Storage/storageAccounts",
          "apiVersion": "2019-06-01",
          "name": "learntemplatestorage123",
          "location": "westus",
          "sku": {
              "name": "Standard_LRS"
          },
          "kind": "StorageV2",
          "properties": {
              "supportsHttpsTrafficOnly": true
          }
      }
   ],
   "outputs": {}
}

templateFile="{provide-the-path-to-the-template-file}"

az deployment group create \

--name blanktemplate \

--resource-group myResourceGroup \

--template-file $templateFile

En resumen, para agregar un recurso a la plantilla, deberemos conocer el proveedor de recursos y sus tipos de recursos. La sintaxis de esta combinación tiene el formato {resource-provider}/{resource-type}. Por ejemplo, para agregar un recurso de cuenta de almacenamiento siguiendo el ejemplo anterior, necesitaremos el proveedor de recursos de almacenamiento de Microsoft. Uno de los tipos de este proveedor es storageAccount. Por lo tanto, el tipo de recurso se mostrará como Microsoft.Storage/storageAccounts. Podemos consultar una lista de proveedores de recursos para los servicios de Azure para encontrar los proveedores que necesitamos en el siguiente enlace.

Una vez definido el proveedor y el tipo de recurso, debemos observar las propiedades de cada tipo de recurso que queremos utilizar.