快速入门:自带存储以创建和发布 Azure 托管应用程序定义

本快速入门介绍如何为 Azure 托管应用程序自带存储 (BYOS)。 可在服务目录中为组织成员创建和发布托管应用程序定义。 当你使用自己的存储帐户时,托管应用程序定义可能会超过服务目录的 120 MB 限制。

若要将托管应用程序定义发布到服务目录,请执行以下任务:

  • 创建一个 Azure 资源管理器模板(ARM 模板),用于定义使用托管应用程序部署的 Azure 资源。
  • 部署托管应用程序时,请定义门户的用户界面元素。
  • 创建包含必需 JSON 文件的 .zip 包。
  • 创建一个用于存储托管应用程序定义的存储帐户。
  • 将托管应用程序定义部署到你自己的存储帐户,使其在服务目录中可用。

如果托管应用程序定义小于 120 MB,并且不希望使用自己的存储帐户,请转到快速入门:创建和发布 Azure 托管应用程序定义

可以使用 Bicep 开发托管应用程序定义,但必须先将其转换为 ARM 模板 JSON,然后才能在 Azure 中发布定义。 有关详细信息,请转到快速入门:使用 Bicep 创建和发布 Azure 托管应用程序定义

还可以使用 Bicep 从服务目录部署托管应用程序定义。 有关详细信息,请转到快速入门:使用 Bicep 部署 Azure 托管应用程序定义

先决条件

若要完成本快速入门,需要准备好以下各项:

创建 ARM 模板

每个托管应用程序定义均包含一个名为 mainTemplate.json 的文件。 该模板定义了要部署的 Azure 资源,与常规 ARM 模板没有什么不同。

打开 Visual Studio Code,创建一个名称区分大小写的文件 mainTemplate.json 并保存它。

添加以下 JSON 并保存文件。 它定义用于部署应用程序服务、应用服务计划和存储帐户的托管应用程序资源。

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "appServicePlanName": {
      "type": "string",
      "maxLength": 40,
      "metadata": {
        "description": "App Service plan name."
      }
    },
    "appServiceNamePrefix": {
      "type": "string",
      "maxLength": 47,
      "metadata": {
        "description": "App Service name prefix."
      }
    },
    "storageAccountNamePrefix": {
      "type": "string",
      "maxLength": 11,
      "metadata": {
        "description": "Storage account name prefix."
      }
    },
    "storageAccountType": {
      "type": "string",
      "allowedValues": [
        "Premium_LRS",
        "Standard_LRS",
        "Standard_GRS"
      ],
      "metadata": {
        "description": "Storage account type allowed values"
      }
    }
  },
  "variables": {
    "appServicePlanSku": "F1",
    "appServicePlanCapacity": 1,
    "appServiceName": "[format('{0}{1}', parameters('appServiceNamePrefix'), uniqueString(resourceGroup().id))]",
    "storageAccountName": "[format('{0}{1}', parameters('storageAccountNamePrefix'), uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "type": "Microsoft.Web/serverfarms",
      "apiVersion": "2022-09-01",
      "name": "[parameters('appServicePlanName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[variables('appServicePlanSku')]",
        "capacity": "[variables('appServicePlanCapacity')]"
      }
    },
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2022-09-01",
      "name": "[variables('appServiceName')]",
      "location": "[parameters('location')]",
      "properties": {
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
        "httpsOnly": true,
        "siteConfig": {
          "appSettings": [
            {
              "name": "AppServiceStorageConnectionString",
              "value": "[format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};Key={2}', variables('storageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2023-01-01').keys[0].value)]"
            }
          ]
        }
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
      ]
    },
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2023-01-01",
      "name": "[variables('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "[parameters('storageAccountType')]"
      },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot",
        "allowSharedKeyAccess": false,
        "minimumTlsVersion": "TLS1_2"
      }
    }
  ],
  "outputs": {
    "appServicePlan": {
      "type": "string",
      "value": "[parameters('appServicePlanName')]"
    },
    "appServiceApp": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.Web/sites', variables('appServiceName')), '2022-09-01').defaultHostName]"
    },
    "storageAccount": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2023-01-01').primaryEndpoints.blob]"
    }
  }
}

定义门户体验

作为发布者,你需要定义创建托管应用程序的门户体验。 createUiDefinition.json 文件生成门户的用户界面。 使用控件元素(如下拉框和文本框)来定义用户如何为每个参数提供输入。

在此例中,用户界面会提示你输入应用程序服务名称前缀、应用服务计划的名称、存储帐户前缀和存储帐户类型。 在部署期间,mainTemplate.json 中的变量使用 uniqueString 函数将包含 13 个字符的字符串追加到名称前缀,以便名称在 Azure 中是全局唯一的。

打开 Visual Studio Code,创建一个名称区分大小写的文件 createUiDefinition.json 并保存它。

将以下 JSON 代码添加到文件并保存它。

{
  "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
  "handler": "Microsoft.Azure.CreateUIDef",
  "version": "0.1.2-preview",
  "parameters": {
    "basics": [
      {}
    ],
    "steps": [
      {
        "name": "webAppSettings",
        "label": "Web App settings",
        "subLabel": {
          "preValidation": "Configure the web app settings",
          "postValidation": "Completed"
        },
        "elements": [
          {
            "name": "appServicePlanName",
            "type": "Microsoft.Common.TextBox",
            "label": "App Service plan name",
            "placeholder": "App Service plan name",
            "defaultValue": "",
            "toolTip": "Use alphanumeric characters or hyphens with a maximum of 40 characters.",
            "constraints": {
              "required": true,
              "regex": "^[a-z0-9A-Z-]{1,40}$",
              "validationMessage": "Only alphanumeric characters or hyphens are allowed, with a maximum of 40 characters."
            },
            "visible": true
          },
          {
            "name": "appServiceName",
            "type": "Microsoft.Common.TextBox",
            "label": "App Service name prefix",
            "placeholder": "App Service name prefix",
            "defaultValue": "",
            "toolTip": "Use alphanumeric characters or hyphens with minimum of 2 characters and maximum of 47 characters.",
            "constraints": {
              "required": true,
              "regex": "^[a-z0-9A-Z-]{2,47}$",
              "validationMessage": "Only alphanumeric characters or hyphens are allowed, with a minimum of 2 characters and maximum of 47 characters."
            },
            "visible": true
          }
        ]
      },
      {
        "name": "storageConfig",
        "label": "Storage settings",
        "subLabel": {
          "preValidation": "Configure the storage settings",
          "postValidation": "Completed"
        },
        "elements": [
          {
            "name": "storageAccounts",
            "type": "Microsoft.Storage.MultiStorageAccountCombo",
            "label": {
              "prefix": "Storage account name prefix",
              "type": "Storage account type"
            },
            "toolTip": {
              "prefix": "Enter maximum of 11 lowercase letters or numbers.",
              "type": "Available choices are Standard_LRS, Standard_GRS, and Premium_LRS."
            },
            "defaultValue": {
              "type": "Standard_LRS"
            },
            "constraints": {
              "allowedTypes": [
                "Premium_LRS",
                "Standard_LRS",
                "Standard_GRS"
              ]
            },
            "visible": true
          }
        ]
      }
    ],
    "outputs": {
      "location": "[location()]",
      "appServicePlanName": "[steps('webAppSettings').appServicePlanName]",
      "appServiceNamePrefix": "[steps('webAppSettings').appServiceName]",
      "storageAccountNamePrefix": "[steps('storageConfig').storageAccounts.prefix]",
      "storageAccountType": "[steps('storageConfig').storageAccounts.type]"
    }
  }
}

若要了解详细信息,请转到 CreateUiDefinition 入门

将文件打包

将这两个文件添加到名为 app.zip 的包文件中。 这两个文件必须都位于该 .zip 文件的根级别。 如果文件位于文件夹中,则当你创建托管应用程序定义时,你会收到一条错误消息,指出所需文件不存在。

app.zip 上传到 Azure 存储帐户,以便在部署托管应用程序的定义时使用它。 存储帐户名称在 Azure 中必须是全局唯一的,长度必须为 3-24 个字符,仅包含小写字母和数字。 在命令中,将占位符<pkgstorageaccountname> (包括尖括号 (<>)) 替换为唯一的存储帐户名称。

New-AzResourceGroup -Name packageStorageGroup -Location chinanorth2

$pkgstorageparms = @{
  ResourceGroupName = "packageStorageGroup"
  Name = "<pkgstorageaccountname>"
  Location = "chinanorth3"
  SkuName = "Standard_LRS"
  Kind = "StorageV2"
  MinimumTlsVersion = "TLS1_2"
  AllowBlobPublicAccess = $true
  AllowSharedKeyAccess = $false
}

$pkgstorageaccount = New-AzStorageAccount @pkgstorageparms

$pkgstorageparms 变量使用 PowerShell splatting 来提高用于创建新存储帐户的命令中使用的参数值的可读性。 Splatting 可用于使用多个参数值的其他 PowerShell 命令。

创建存储帐户后,将角色分配存储 Blob 数据参与者添加到存储帐户范围中。 分配对 Microsoft Entra 用户帐户的访问权限。 根据 Azure 中的访问级别,你可能需要管理员分配的其他权限。 有关详细信息,请参阅分配用于访问 Blob 数据的 Azure 角色

向存储帐户添加角色后,需要几分钟才能在 Azure 中激活。 然后,可以创建创建容器和上传文件所需的上下文。

$pkgstoragecontext = New-AzStorageContext -StorageAccountName $pkgstorageaccount.StorageAccountName -UseConnectedAccount

New-AzStorageContainer -Name appcontainer -Context $pkgstoragecontext -Permission blob

$blobparms = @{
  File = "app.zip"
  Container = "appcontainer"
  Blob = "app.zip"
  Context = $pkgstoragecontext
}

Set-AzStorageBlobContent @blobparms

使用以下命令将包文件的 URI 存储在名为 packageuri 的变量中。 将在部署托管应用程序定义时使用该变量的值。

$packageuri=(Get-AzStorageBlob -Container appcontainer -Blob app.zip -Context $pkgstoragecontext).ICloudBlob.StorageUri.PrimaryUri.AbsoluteUri

自带符合托管应用程序定义的存储

将托管应用程序定义存储在你自己的存储帐户中,以便你可以根据监管需求管理其位置和访问权限。 通过使用自己的存储帐户,你的应用程序可以超过服务目录托管应用程序定义的 120 MB 限制。

注意

仅在 ARM 模板或托管应用程序定义的 REST API 部署中支持自带存储。

创建存储帐户

为托管应用程序定义创建存储帐户。 存储帐户名称在 Azure 中必须是全局唯一的,长度必须为 3-24 个字符,仅包含小写字母和数字。

此示例创建一个名为 byosDefinitionStorageGroup 的新资源组。 在命令中,将占位符<byosaccountname> (包括尖括号 (<>)) 替换为唯一的存储帐户名称。

New-AzResourceGroup -Name byosDefinitionStorageGroup -Location chinanorth2

$byostorageparms = @{
  ResourceGroupName = "byosDefinitionStorageGroup"
  Name = "<byosaccountname>"
  Location = "chinanorth3"
  SkuName = "Standard_LRS"
  Kind = "StorageV2"
  MinimumTlsVersion = "TLS1_2"
  AllowBlobPublicAccess = $true
  AllowSharedKeyAccess = $true
}

$byosstorageaccount = New-AzStorageAccount @byostorageparms

创建存储帐户后,将角色分配存储 Blob 数据参与者添加到存储帐户范围中。 分配对 Microsoft Entra 用户帐户的访问权限。 此过程后面的步骤需要访问权限。

向存储帐户添加角色后,需要几分钟才能在 Azure 中激活。 然后,可以创建创建容器和上传文件所需的上下文。

$byosstoragecontext = New-AzStorageContext -StorageAccountName $byosstorageaccount.StorageAccountName -UseConnectedAccount

使用以下命令将存储帐户的资源 ID 存储在名为 byosstorageid 的变量中。 将在部署托管应用程序定义时使用该变量的值。

$byosstorageid = (Get-AzStorageAccount -ResourceGroupName $byosstorageaccount.ResourceGroupName -Name $byosstorageaccount.StorageAccountName).Id

为存储帐户设置角色分配

在将托管应用程序定义部署到存储帐户之前,请将“参与者”角色分配给存储帐户范围内的“设备资源提供程序”用户。 此分配允许标识将定义文件写入存储帐户的容器。

可以使用变量来设置角色分配。 此示例使用在上一步中创建的 $byosstorageid 变量并创建 $arpid 变量。

$arpid = (Get-AzADServicePrincipal -SearchString "Appliance Resource Provider").Id

New-AzRoleAssignment -ObjectId $arpid -RoleDefinitionName Contributor -Scope $byosstorageid

设备资源提供程序 是 Microsoft Entra 租户中的服务主体。 从 Azure 门户中,可以验证是否已将其注册,方法是转到“Microsoft Entra ID”>“企业应用程序”并将搜索筛选器更改为“Microsoft 应用程序”。 搜索设备资源提供程序。 如果未找到,请注册Microsoft.Solutions 资源提供程序。

获取组 ID 和角色定义 ID

下一步是选择用于为客户管理资源的用户、安全组或应用程序。 此标识对受管理资源组的权限与所分配的角色相对应。 角色可以是任何 Azure 内置角色,例如所有者或参与者。

此示例使用安全组,并且 Microsoft Entra 帐户应是组的成员。 若要获取组的对象 ID,请将占位符<managedAppDemo> ((包括尖括号 (<>)) 替换为组的名称。 将在部署托管应用程序定义时使用该变量的值。

若要创建新的 Microsoft Entra 组,请转到管理 Microsoft Entra 组和组成员身份

$principalid=(Get-AzADGroup -DisplayName <managedAppDemo>).Id

接下来,获取希望将其访问权限授予用户、组或应用程序的 Azure 内置角色的角色定义 ID。 将在部署托管应用程序定义时使用该变量的值。

$roleid=(Get-AzRoleDefinition -Name Owner).Id

创建定义部署模板

使用 Bicep 文件在服务目录中部署托管应用程序定义。 部署后,定义文件将存储在你自己的存储帐户中。

打开 Visual Studio Code,创建一个名为 deployDefinition.bicep 的文件并保存它。

添加以下 Bicep 代码并保存文件。

param location string = resourceGroup().location

@description('Name of the managed application definition.')
param managedApplicationDefinitionName string

@description('Resource ID for the bring your own storage account where the definition is stored.')
param definitionStorageResourceID string

@description('The URI of the .zip package file.')
param packageFileUri string

@description('Publishers Principal ID that needs permissions to manage resources in the managed resource group.')
param principalId string

@description('Role ID for permissions to the managed resource group.')
param roleId string

var definitionLockLevel = 'ReadOnly'
var definitionDisplayName = 'Sample BYOS managed application'
var definitionDescription = 'Sample BYOS managed application that deploys web resources'

resource managedApplicationDefinition 'Microsoft.Solutions/applicationDefinitions@2021-07-01' = {
  name: managedApplicationDefinitionName
  location: location
  properties: {
    lockLevel: definitionLockLevel
    description: definitionDescription
    displayName: definitionDisplayName
    packageFileUri: packageFileUri
    storageAccountId: definitionStorageResourceID
    authorizations: [
      {
        principalId: principalId
        roleDefinitionId: roleId
      }
    ]
  }
}

有关模板属性的详细信息,请转到 Microsoft.Solutions/applicationDefinitions

受管理资源组上的 lockLevel 可防止客户对此资源组执行不良操作。 目前,ReadOnly 是唯一受支持的锁定级别。 ReadOnly 指定客户只能读取受管理资源组中存在的资源。 授予对受管理资源组的访问权限的发布者标识不受该锁定级别控制。

创建参数文件

托管应用程序定义的部署模板需要用户输入多个参数。 部署命令会提示输入值,你也可以为值创建参数文件。 在此示例中,我们使用参数文件将参数值传递给部署命令。

在 Visual Studio Code 中,创建名为 deployDefinition-parameters.bicepparam 的新文件并保存它

将以下内容添加到参数文件并保存它。 然后,将 <placeholder values> (包括大括号(<>)) 替换为你的值。

using './deployDefinition.bicep'

param managedApplicationDefinitionName = 'sampleByosManagedApplication'
param definitionStorageResourceID = '<placeholder for you BYOS storage account ID>'
param packageFileUri = '<placeholder for the packageFileUri>'
param principalId = '<placeholder for principalid value>'
param roleId = '<placeholder for roleid value>'

下表描述了托管应用程序定义的参数值。

参数
managedApplicationDefinitionName 托管应用程序定义的名称。 对于此示例,请使用 sampleByosManagedApplication
definitionStorageResourceID 用于存储定义的存储帐户的资源 ID。 使用 byosstorageid 变量的值。
packageFileUri 输入 .zip 包文件的 URI。 使用 packageuri 变量的值。 格式为 https://yourStorageAccountName.blob.core.chinacloudapi.cn/appcontainer/app.zip
principalId 需要权限来管理受管理资源组中的资源的发布者主体 ID。 使用 principalid 变量的值。
roleId 对受管理资源组具有访问权限的角色 ID。 例如“所有者”、“参与者”、“读者”。 使用 roleid 变量的值。

若要获取变量值,请执行以下操作:

  • Azure PowerShell:在 PowerShell 中,输入 $variableName 以显示变量的值。
  • Azure CLI:在 Bash 中,输入 echo $variableName 以显示变量的值。

部署定义

部署托管应用程序的定义时,它将在服务目录中可用。 此过程不会部署托管应用程序的资源。

创建一个名为 byosAppDefinitionGroup 的资源组,并将托管应用程序定义部署到你的存储帐户。

New-AzResourceGroup -Name byosAppDefinitionGroup -Location chinanorth3

$deployparms = @{
  ResourceGroupName = "byosAppDefinitionGroup"
  TemplateFile = "deployDefinition.bicep"
  TemplateParameterFile = "deployDefinition-parameters.bicepparam"
  Name = "deployDefinition"
}

New-AzResourceGroupDeployment @deployparms

验证定义文件存储

在部署期间,模板的 storageAccountId 属性使用存储帐户的资源 ID 并创建一个名称区分大小写的新容器 applicationdefinitions。 在部署期间指定的 .zip 包中的文件存储在新容器中。

可以使用以下命令来验证托管应用程序定义文件是否保存在存储帐户的容器中。 在命令中,将占位符<byosaccountname> (包括尖括号 (<>)) 替换为唯一的存储帐户名称。

Get-AzStorageContainer -Name applicationdefinitions -Context $byosstoragecontext |
Get-AzStorageBlob | Select-Object -Property Name | Format-List

注意

为了增加安全性,你可以创建托管应用程序定义并将其存储在启用了加密的 Azure 存储帐户 Blob 中。 通过存储帐户的加密选项来加密定义内容。 只有有权访问文件的用户才能访问服务目录中的定义。

更新存储帐户安全性

成功部署后,为了提高存储帐户的安全性,请禁用共享访问密钥属性。 创建存储帐户时,为存储 Blob 数据参与者添加了角色分配,使你无需使用存储密钥即可访问容器和 Blob

要查看和更新存储帐户的共享访问密钥设置,请使用以下命令:

(Get-AzStorageAccount -ResourceGroupName $byosstorageaccount.ResourceGroupName -Name $byosstorageaccount.StorageAccountName).AllowSharedKeyAccess

Set-AzStorageAccount -ResourceGroupName $byosstorageaccount.ResourceGroupName -Name $byosstorageaccount.StorageAccountName -AllowSharedKeyAccess $false

请确保用户可以访问你的定义

你可以访问托管应用程序定义,但你希望确保组织中的其他用户可以访问它。 至少授予他们对定义的读者角色。 他们可能已从订阅或资源组继承了此级别的访问权限。 若要检查谁有权访问定义并添加用户或组,请转到使用 Azure 门户分配 Azure 角色

清理资源

如果要部署定义,请继续阅读链接到该文章的“后续步骤”部分来部署定义。

如果已完成托管应用程序定义,则可以删除创建的名为 packageStorageGroup、byosDefinitionStorageGroup 和 byosAppDefinitionGroup 的资源组。

命令会提示你确认删除资源组。

Remove-AzResourceGroup -Name packageStorageGroup

Remove-AzResourceGroup -Name byosDefinitionStorageGroup

Remove-AzResourceGroup -Name byosAppDefinitionGroup

后续步骤

已发布托管应用程序定义。 现在,了解如何部署该定义的实例。