Azure: ARM — ручное создание шаблона

Автор: | 09/01/2016
 

azure_logoДля приложения требуется создать Resource Group, в которую будут входить:

  1. один WebApp;
  2. один SQL сервер;
  3. две SQL базы;
  4. один Storage Account.

За основу — можно взять готовый шаблон от Azure («Фягку» 😀 ) отсюда>>>.

Шаблон, который получился в результате написания поста, можно посмотреть тут>>>.

Общий обзор по работе с Azure Resource Manager и созданию шаблонов — в посте Azure: Resource manager, Azure CLI и деплой resource group.

Переключаем подписку:

$ azure account list
info:    Executing command account list
data:    Name                                                Id                                    Current  State   
data:    --------------------------------------------------  ------------------------------------  -------  --------
data:    Pay-As-You-Go                                       fe37db50-***-***-***-66145cdbd735  false    Disabled
...
data:    Meister PII                                    e7aac243-***-***-***-c2493a6a3a7b  false    Enabled 
$ azure account set 0a4f2b9c-***-***-***-40b17ef8c3ab
info:    Executing command account set
info:    Setting subscription to "Pay-As-You-Go" with id "0a4f2b9c-***-***-***-40b17ef8c3ab".
info:    Changes saved

Качаем шаблон:

$ git clone https://github.com/Azure/azure-quickstart-templates.git
$ cd 201-web-app-sql-database

Переименовываем файлы:

$  mv azuredeploy.json jm-platform.json
$  mv azuredeploy.parameters.json jm-platform.parameters.json

Создание группы ресурсов

Используя Azure CLI — запускаем создание группы из этого шаблона с опциями:

  • -l: регион Azure;
  • -n: имя группы;
  • -d: имя деплоя;
  • -f: файл шаблона;
  • -e: файл с параметрами для шаблона.
$ azure group create -l westeurope -n jm-platfrom-dev-eu-2 -d InitDeploy -f jm-platform.json -e jm-platform.parameters.json 
info:    Executing command group create
+ Getting resource group jm-platfrom-dev-eu-2
+ Creating resource group jm-platfrom-dev-eu-2
info:    Created resource group jm-platfrom-dev-eu-2
+ Initializing template configurations and parameters
+ Creating a deployment
error:   Long running operation failed with error: 'At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.'.
info:    Error information has been recorded to /home/setevoy/.azure/azure.err
error:   group create command failed

Ошибка сейчас — ОК, смотрим лог /home/setevoy/.azure/azure.err log:

...
   { body: '{"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.","details":[{"code":"BadRequest","message":"{\\r\\n  \\"code\\": \\"40632\\",\\r\\n  \\"message\\": \\"Password validation failed. The password does not meet policy requirements because it is not complex enough.
...

Генерируем пароль:

$ pwgen 12
ohba4ebu3Wee oez2iew8Se3S ooru7Eeniet3 Quohci4que6x eiGie9ohwooY eeno2iC6shee
ahR1iceephe9 ahte7heisieX Diex7eiGe7in wie9Awei0Wae tohDie6aghie ooM4eeChahng
dai5ieNgu0bi Shie3Choh0vo amie1yo7oThi ie1edeeCieku teeDoh6Bohph Kee9Ahch9Qui
...

Обновляем jm-platform.parameters.json, меняем:

...
    "administratorLoginPassword": {
      "value": "GEN-PASSWORD"                  
    }
...

На:

    "administratorLoginPassword": {
      "value": "eeno2iC6shee"                  
    }

Удаляем группу:

$ azure group list
info:    Executing command group list
+ Listing resource groups                                                      
data:    Name                                               Location        Provisioning State  Tags:
data:    -------------------------------------------------  --------------  ------------------  -----
data:    DNS                                                westeurope      Succeeded           null 
data:    europe-jm                                          westeurope      Succeeded           null 
data:    jm-platfrom-dev-eu                            westeurope      Succeeded           null 
data:    jm-platfrom-dev-eu-2                          westeurope      Succeeded           null 
$ azure group delete -n jm-platfrom-dev-eu-2
info:    Executing command group delete
Delete resource group jm-platfrom-dev-eu-2? [y/n] y
+ Deleting resource group jm-platfrom-dev-eu-2

Пробуем:

$ azure group create -l westeurope -n jm-platfrom-dev-eu-2 -d InitDeploy -f jm-platform.json -e jm-platform.parameters.json 
info:    Executing command group create
+ Getting resource group jm-platfrom-dev-eu-2
+ Creating resource group jm-platfrom-dev-eu-2
info:    Created resource group jm-platfrom-dev-eu-2
+ Initializing template configurations and parameters
+ Creating a deployment
info:    Created template deployment "InitDeploy"
data:    Id:                  /subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-platfrom-dev-eu-2
data:    Name:                jm-platfrom-dev-eu-2
data:    Location:            westeurope
data:    Provisioning State:  Succeeded
data:    Tags: null
data:    
info:    group create command OK

azure-arm-platfrom-1

Обновление шаблона

...
      "type": "Microsoft.Insights/autoscalesettings",
...
      "type": "Microsoft.Insights/alertrules",
...
      "type": "Microsoft.Insights/components",
...

AutoscaleSettings, Application Insights и AlertRules — для приложения не нужны, убираем их из ресурсов.

Выносим имя баз (т.к. их будет две) в переменные.

В файле шаблона — убираем:

...
    "databaseName": {
      "type": "string",
      "defaultValue": "sampledb",
      "metadata": {
        "description": "The name of the new database to create."
      }
    },
...

Там же находим переменные:

...
  "variables": {
    "hostingPlanName": "[concat('hostingplan', uniqueString(resourceGroup().id))]",
    "webSiteName": "[concat('webSite', uniqueString(resourceGroup().id))]",
    "sqlserverName": "[concat('sqlserver', uniqueString(resourceGroup().id))]"
  },
...

И добавляем две переменные для двух баз данных:

...
  "variables": {
    "hostingPlanName": "[concat('hostingplan', uniqueString(resourceGroup().id))]",
    "webSiteName": "[concat('webSite', uniqueString(resourceGroup().id))]",
    "sqlserverName": "[concat('sqlserver', uniqueString(resourceGroup().id))]",
    "sqlDBbasename": "[concat('sql-DB-', resourceGroup().name)]",
    "sqlCMSbasename": "[concat('sql-CMS-', resourceGroup().name)]"
  },
...

Описание объекта resourceGroup() есть тут>>>, а функции uniqueString()тут>>>.

Находим ресурс создания SQL сервера:

...
  "resources": [
    {
      "name": "[variables('sqlserverName')]",
      "type": "Microsoft.Sql/servers",
...

У которого есть вложенный блок resources с базой данных:

...
      "resources": [
        {
          "name": "[parameters('databaseName')]",
          "type": "databases",
...

Дублируем его и обновляем имя — вместо [parameters('databaseName')] используем переменные sqlCMSbasename и sqlDBbasename:

...
      "resources": [
        {
          "name": "",
          "type": "databases",
          "location": "[resourceGroup().location]",
          "tags": {
            "displayName": "DB Database"
          },
          "apiVersion": "2014-04-01-preview",
          "dependsOn": [
            "[variables('sqlserverName')]"
          ],
          "properties": {
            "edition": "[parameters('edition')]",
            "collation": "[parameters('collation')]",
            "maxSizeBytes": "[parameters('maxSizeBytes')]",
            "requestedServiceObjectiveName": "[parameters('requestedServiceObjectiveName')]"
          }
        },
        {
          "name": "[variables('sqlCMSbasename')]",
          "type": "databases",
          "location": "[resourceGroup().location]",
          "tags": {
            "displayName": "CMS Database"
          },
          "apiVersion": "2014-04-01-preview",
          "dependsOn": [
            "[variables('sqlserverName')]"
          ],
          "properties": {
            "edition": "[parameters('edition')]",
            "collation": "[parameters('collation')]",
            "maxSizeBytes": "[parameters('maxSizeBytes')]",
            "requestedServiceObjectiveName": "[parameters('requestedServiceObjectiveName')]"
          }
        },
...

Далее, находим connectionstrings:

...
      "resources": [
        {
          "apiVersion": "2015-08-01",
          "type": "config",
          "name": "connectionstrings",
          "dependsOn": [
            "[variables('webSiteName')]"
          ],
          "properties": {
            "DefaultConnection": {
              "value": "[concat('Data Source=tcp:', reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName, ',1433;Initial Catalog=', parameters('databaseName'), ';User Id=', parameters('administratorLogin'), '@', variables('sqlserverName'), ';Password=', parameters('administratorLoginPassword'), ';')]",
              "type": "SQLServer"
...

Обновляем — добавляем вторую базу и обновляем имя в parameters('databaseName') — тоже используем переменные:

...
          "properties": {
            "sqlDBConnection": {
              "value": "[concat('Data Source=tcp:', reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName, ',1433;Initial Catalog=', variables('sqlDBbasename'), ';User Id=', parameters('administratorLogin'), '@', variables('sqlserverName'), ';Password=', parameters('administratorLoginPassword'), ';')]",
              "type": "SQLServer"
            },
            "sqlCMSConnection": {
              "value": "[concat('Data Source=tcp:', reference(concat('Microsoft.Sql/servers/', variables('sqlserverName'))).fullyQualifiedDomainName, ',1433;Initial Catalog=', variables('sqlCMSbasename'), ';User Id=', parameters('administratorLogin'), '@', variables('sqlserverName'), ';Password=', parameters('administratorLoginPassword'), ';')]",
              "type": "SQLServer"
            }
          }
...

Запускаем:

$ azure group create -l westeurope -n jm-platfrom-dev-eu-3 -d DBsNamesChanged -f azuredeploy.json -e azuredeploy.parameters.json
info:    Executing command group create
+ Getting resource group jm-platfrom-dev-eu-3
+ Creating resource group jm-platfrom-dev-eu-3
info:    Created resource group jm-platfrom-dev-eu-3
+ Initializing template configurations and parameters
+ Creating a deployment
info:    Created template deployment "DBsNamesChanged"
data:    Id:                  /subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-platfrom-dev-eu-3
data:    Name:                jm-platfrom-dev-eu-3
data:    Location:            westeurope
data:    Provisioning State:  Succeeded
data:    Tags: null
data:    
info:    group create command OK

Проверяем:

$ azure group log show jm-platfrom-dev-eu-3 --last-deployment | grep "sql-"
                                       /sql-DB-jm-platfrom-dev-eu-3
                               sqlserverjnknw5oaslkpe/databases/sql-DB-jm-
                                       /sql-CMS-jm-platfrom-dev-eu-3
                               sqlserverjnknw5oaslkpe/databases/sql-CMS-jm-
                                       /sql-DB-jm-platfrom-dev-eu-3

Базы с новыми именами готовы.

Приводим к нормальному виду остальные имена:

...
  "variables": {
    "hostingPlanName": "[concat(resourceGroup().name, '-app-plan')]",
    "webSiteName": "[concat(resourceGroup().name, '-app')]",
    "sqlserverName": "[concat(resourceGroup().name, '-sql-server')]",
    "sqlDBbasename": "[concat(resourceGroup().name, '-DB-database')]",
    "sqlCMSbasename": "[concat(resourceGroup().name, '-CMS-database')]"
  },
...

Storage Account

Для статики приложения (Umbraco CMS) — используется Blob Storage.

Как пример — можно взять шаблон отсюда>>> (одно из немногих достоинств Azure — наличие большой коллекции таких шаблонов на все случаи жизни, удобный поиск вот тут>>>).

В файл параметров вносим storageAccountType:

...
    "administratorLoginPassword": {		
      "value": "eeno2iC6shee"
    },
    "storageAccountType": {
      "value": "Standard_GRS"
    }
  }
...

В файле шаблона — добавляем переменную с именем аккаунта.

Увы — тут не выйдет использовать атрибут resourceGroup().name, т.к. имя Storage не должно содержать тире. Снова используем uniquestring() для генерации случайного имени из атрибута id объекта resourceGroup:

...
    "sqlCMSbasename": "[concat(resourceGroup().name, '-CMS-database')]",
    "storageAccountName": "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'storage')]"
  },
...

В конец блока resources добавляем создание хранилища:

...
      {
      "type": "Microsoft.Storage/storageAccounts",
      "name": "[variables('storageAccountName')]",
      "apiVersion": "2016-01-01",
      "dependsOn": [
        "[variables('storageAccountName')]"
      ],
      "location": "[resourceGroup().location]",
      "sku": {
          "name": "[parameters('storageAccountType')]"
      },
      "kind": "Storage",
        "properties": {
        }
      }
...

azure-arm-platfrom-2

Примечание: наткнулся на сообщение об ошибке, которая меня в первый момент очень «удивила»:

error:   Deployment template validation failed: ‘The resource ‘/subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-platfrom-dev-eu-1/providers/Microsoft.Storage/storageAccounts/jm-platfrom-dev-eu-1-storage’ at line ‘1’ and column ‘3561’ doesn’t depend on parent resource ‘/subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-platfrom-dev-eu-1/providers/Microsoft.Sql/servers/jm-platfrom-dev-eu-1-sql-server’. Please add dependency explicitly using the ‘dependsOn’ syntax. Please see https://aka.ms/arm-template/#resources for usage details.’.

Казалось бы — при чём тут ресурс SQL сервера — к Storage Account-у? Оказалось — я "type": "Microsoft.Storage/storageAccounts" добавил внутрь блока ресурса "type": "Microsoft.Sql/servers", вместо добавления его отдельным блоком.

Запускаем, проверяем:

$ azure group delete -n jm-platfrom-dev-eu-3
$ azure group create -l westeurope -n jm-platfrom-dev-eu-1 -d StorageAccAdd -f jm-platform.json -e jm-platform.parameters.json 
info:    Executing command group create
+ Getting resource group jm-platfrom-dev-eu-1
+ Updating resource group jm-platfrom-dev-eu-1
info:    Updated resource group jm-platfrom-dev-eu-1
+ Initializing template configurations and parameters
+ Creating a deployment
error:   Deployment template validation failed: 'The template parameters 'storageAccountType' in the parameters file are not valid; they are not present in the original template and can therefore not be provided at deployment time. The only supported parameters for this template are 'skuName, skuCapacity, administratorLogin, administratorLoginPassword, collation, edition, maxSizeBytes, requestedServiceObjectiveName'. Please see https://aka.ms/arm-deploy/#parameter-file for usage details.'.
info:    Error information has been recorded to /home/setevoy/.azure/azure.err
error:   group create command failed

Упс.

Возвращаемся к файлу шаблона, и в начале файла, в конеце блока parameters добавляем параметр storageAccountType:

...
    "storageAccountType": {
      "type": "string",
      "defaultValue": "Standard_LRS",
      "allowedValues": [
        "Standard_LRS",
        "Standard_GRS",
        "Standard_ZRS",
        "Premium_LRS"
      ],
      "metadata": {
        "description": "Storage Account type"
      }
    }
...

Повторяем:

$ azure group delete -n jm-platfrom-dev-eu-3
$ azure group create -l westeurope -n jm-platfrom-dev-eu-1 -d StorageAccAdd -f jm-platform.json -e jm-platform.parameters.json
+ Getting resource group jm-platfrom-dev-eu-1
+ Creating resource group jm-platfrom-dev-eu-1
info:    Created resource group jm-platfrom-dev-eu-1
+ Initializing template configurations and parameters
+ Creating a deployment
info:    Created template deployment "StorageAccAdd"
data:    Id:                  /subscriptions/0a4f2b9c-***-***-***-40b17ef8c3ab/resourceGroups/jm-platfrom-dev-eu-1
data:    Name:                jm-platfrom-dev-eu-1
data:    Location:            westeurope
data:    Provisioning State:  Succeeded
data:    Tags: null
data:    
info:    group create command OK

Проверяем:

$ azure storage account list | grep dev-eu
data:    5depgbnaltmzmstorage  Standard_GRS    westeurope  jm-platfrom-dev-eu-1

Firewall

Для доступа к SQL сереру — требуется открыть доступ из нашей сети.

Находим:

...
        {
          "type": "firewallrules",
          "apiVersion": "2014-04-01-preview",
          "dependsOn": [
            "[variables('sqlserverName')]"
          ],
          "location": "[resourceGroup().location]",
          "name": "AllowAllWindowsAzureIps",
          "properties": {
            "endIpAddress": "0.0.0.0",
            "startIpAddress": "0.0.0.0"
          }
        }
...

Меняем на свои IP:

...
          "properties": {
            "endIpAddress": "194.***.***.45",
            "startIpAddress": "194.***.***.45"
...

Deployment Slot

Для деплоя на WebApp — добавим Deployment slot.

Находим ресурс "type": "Microsoft.Web/sites" и внутрь его блока resources — добавляем создание слота:

...
        {
          "apiVersion": "2015-08-01",
            "name": "[variables('slotName')]",
            "type": "slots",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/Sites', variables('webSiteName'))]"
            ],
            "properties": {},
            "resources": [
            ]
        }
...

В переменные добавляем имя слота:

...
"slotName": "staging"
...

Далее, что бы избежать ошибки:

«Cannot complete the operation because the site will exceed the number of slots allowed for the \’Free\’ SKU.\\»

обновляем параметр skuName, и устанавливаем defaultValue == S1 (Pricing Tier Standard: 2 Small):

Кто-то из читающих — видели описание этих планов? Кроме вот этого>>> — не нагулил ничего. А по этой ссылке — описания ограничений на кол-во инстансов не увидел, есть только в Portal-е.

F1 — B3 можно удалить:

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "skuName": {
      "type": "string",
      "defaultValue": "S1",
      "allowedValues": [
        "S1",
        "S2",
        "S3",
        "P1",
        "P2",
        "P3",
        "P4"
      ],
...

Теги

Для удобства — добавим теги для создаваемых ресурсов.

Для SQL сервера можно указать так:

...
  "resources": [
    {
      "name": "[variables('sqlserverName')]",
      "type": "Microsoft.Sql/servers",
      "location": "[resourceGroup().location]",
      "tags": {
        "Application": "JM Platform"
      },
...

Повторяем для остальных. например — для WebApp:

...
      "tags": {
        "Application": "JM Platform WebApp"
      },
...

В целом — готово, можно деплоить группы.

По ходу дела — нашёл хороший шаблон для полного деплоя (не только инфрастуктуры) Umbraco CMS вот тут>>>.

Ссылки по теме

Provision a web app with a SQL Database

Azure: Resource manager, Azure CLI и деплой resource group

Authoring Azure Resource Manager templates

Azure Resource Manager template functions (хоть что-то по функциям ARM)

Simple bash scripting for “azure” cli (не совсем по теме поста — но несколько хороших примеров)