以编程方式部署市场产品

本文介绍如何使用 Azure CLI、Azure PowerShell 和 Terraform 将市场产品资源部署到 Azure。

先决条件

需安装 Azure PowerShell 并连接到 Azure:

部署命令在 Azure CLI 版本 2.2.0 中已更改。 本文中的示例需要 Azure CLI 2.20.0 或更高版本

若要运行此示例,请安装最新版本的 Azure CLI。 若要开始,请运行 az 登录以创建与 Azure 的连接。

如何查找发布者、产品/服务和计划的市场产品标识符

若要以编程方式部署市场产品,必须先获取市场产品的唯一标识符。

若要查找唯一标识符,

  1. 打开Azure 门户并导航到市场体验。
  2. 搜索要部署的市场产品
  3. 通过选择产品名称打开产品详细信息页。
  4. 导航到“ 使用情况信息 + 支持 ”选项卡。在“使用情况信息”中,将显示发布者 ID、产品 ID 和计划 ID。

产品 ID 页的屏幕截图。

注意

在某些 API 中,产品 ID 也称为套餐 ID,计划 ID 也称为 SKU ID。

Azure 市场中的虚拟机

若要从Azure 市场部署第三方 VM,需要首先接受要部署的 VM 映像的最终用户许可协议(EULA)。 在 Azure 订阅中接受 EULA 一次后,应该能够再次部署同一 VM 产品/服务,而无需再次接受条款。 如果要从Azure 门户部署 VM,则在此处接受条款。 但是,以编程方式执行部署时,需要接受使用 az vm image terms accept --publisher X --offer Y --plan Z ARM 或使用 ARM 的条款。

如果尚未接受条款,将显示以下错误:

Code : MarketplacePurchaseEligibilityFailed
Message: Marketplace purchase eligibility check returned errors. See inner errors for details
Details: Offer with PublisherId: '<PublisherId>', OfferId: '<OfferId>' cannot be purchased due to validation errors. For more information see details. Correlation Id: '11111111-1111-1111-1111-111111111111' You have not accepted the legal terms on this subscription: '11111111-1111-1111-1111-111111111111' for this plan. Before the subscription can be used, you need to accept the legal terms of the image. To read and accept legal terms, use the Azure CLI commands described at https://go.microsoft.com/fwlink/?linkid=2110637 or the PowerShell commands available at https://go.microsoft.com/fwlink/?linkid=862451. Alternatively, deploying via the Azure portal provides a UI experience for reading and accepting the legal terms.

使用 Azure CLI 从 Azure 市场部署 VM

接受条款后,可以使用 ARM/Bicep 模板、Azure CLI、Terraform 等常规方法部署 VM。

若要了解有关查找 VM 映像的详细信息,请接受条款并使用 CLI 部署 VM,请参阅 使用 CLI 查找和使用市场购买计划信息。

使用 Terraform 从 Azure 市场部署 VM

有关如何使用 Terraform for Windows VM 或 Linux VM 部署虚拟机的说明。

若要使用 Terraform 部署市场 VM,必须执行以下操作:

  1. 使用 azurerm_marketplace_agreement 接受 VM 产品法律条款

  2. planazurerm_virtual_machine提供程序中指定块

注意

如果未指定计划块,部署将失败并出现以下错误:

Code: VMMarketplaceInvalidInput Message: Creating a virtual machine from Marketplace image or a custom image sourced from a Marketplace image requires Plan information in the request. VM: '/subscriptions/<Subscription ID>/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/myVM

注意

azurerm_marketplace_agreement被视为 Terraform 资源,因此,首次创建特定市场 VM 产品时,会创建唯一的资源来表示法律条款被接受的事实。 但是,下次尝试在同一 Azure 订阅下部署另一个 VM(具有相同的发布服务器 ID 和套餐 ID),将收到错误:

A resource with the ID "/subscriptions/11111111-1111-1111-1111-111111111111 /providers/Microsoft.MarketplaceOrdering/agreements/<Publisher ID>/offers/<Product ID>/plans/<Plan ID>" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_marketplace_agreement" for more information

需要运行 Terraform state list 以查看 是否有azurerm_marketplace_agreement 资源状态,并且不需要将资源状态导入 Terraform 状态。

terraform import azurerm_marketplace_agreement.<TerraformResourceName> /subscriptions/<AzureSubscriptionId>/providers/Microsoft.MarketplaceOrdering/agreements/<Publisher ID>/offers/<Product ID>/plans/<Plan ID>

Azure 市场中的 SaaS 产品/服务

SaaS 产品/服务通常由客户通过Azure 门户进行部署。 使用Azure 门户部署 SaaS 产品/服务后,客户使用“立即配置帐户”按钮访问 SaaS ISV 的登陆页面并完成 SaaS 产品/服务配置。 配置产品/服务后,SaaS ISV 使用 SaaS 履行 API 激活它。

通过 Azure 门户 部署 SaaS 产品/服务时,它会创建 ARM 模板并为部署分配特定的参数值。 其中一个参数是 termId,用于标识套餐的订阅期限。 termId 值不是静态的,但取决于产品/服务配置和部署时间。 因此,不能对 ARM 模板中的 termId 使用固定值。 相反,需要按照以下步骤找出当前的 termId 值:

  1. 通过Azure 门户手动部署产品/服务。
  2. 转到部署套餐的资源组。
  3. 选择“部署”部分下的部署名称。
  4. 查看输入参数并复制 termId 的值。

如果给定的 SaaS 产品/服务从未部署到 Azure 订阅中,则编程部署失败,并出现如下错误:

code: DeploymentFailed

message: At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage

Details: Failed to process eligibility check with error Purchase has failed due to signature verification on Marketplace legal agreement. Please retry. If error persists use different Azure subscription, or contact support with correlation-id '11111111-1111-1111-1111-111111111111' and this error message

使用 ARM 模板和 Azure CLI 部署 SaaS 产品/服务

请参阅 ARM 示例模板。

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "name": {
            "type": "string"
        },
        "planId": {
            "type": "string"
        },
        "offerId": {
            "type": "string"
        },
        "publisherId": {
            "type": "string"
        },
        "quantity": {
            "type": "int"
        },
        "termId": {
            "type": "string"
        },
        "azureSubscriptionId": {
            "type": "string"
        },
        "publisherTestEnvironment": {
            "type": "string",
            "defaultValue": ""
        },
        "autoRenew": {
            "type": "bool"
        }
    },
    "resources": [
        {
            "type": "Microsoft.SaaS/resources",
            "apiVersion": "2018-03-01-beta",
            "name": "[parameters('name')]",
            "location": "global",
            "properties": {
                "saasResourceName": "[parameters('name')]",
                "publisherId": "[parameters('publisherId')]",
                "SKUId": "[parameters('planId')]",
                "offerId": "[parameters('offerId')]",
                "quantity": "[parameters('quantity')]",
                "termId": "[parameters('termId')]",
                "autoRenew": "[parameters('autoRenew')]",
                "paymentChannelType": "SubscriptionDelegated",
                "paymentChannelMetadata": {
                    "AzureSubscriptionId": "[parameters('azureSubscriptionId')]"
                },
                "publisherTestEnvironment": "[parameters('publisherTestEnvironment')]",
                "storeFront": "AzurePortal"
            }
        }
    ]
}

  • 将上述内容另存为 SaaS-ARM.json
  • 运行以下命令:
az group create --resource-group <ResourceGroupName> --location <Location>

az deployment group create --resource-group <Resource Group Name> --template-file ./SaaS-ARM.json --parameters name=<SaaS Resource Name> publisherId=<Publisher ID> offerId=<Product ID> planId=<Plan ID> termId=<termId> quantity=1 azureSubscriptionId=11111111-1111-1111-1111-11111111 autoRenew=true

预配 SaaS 产品/服务资源后,可以调用以下 ARM API 来查看其属性:

az rest --method get --uri /subscriptions/<AzureSubscriptionId>/resourceGroups/<ResourceGroupName>/providers/Microsoft.SaaS/resources/<SaaS Resource Name>?api-version=2018-03-01-beta -o json

现在可以调用 POST 以获取市场令牌和登陆页面 URL。 此 URL 可用于浏览 SaaS ISV 的登陆页面以完成配置和激活 SaaS 产品/服务。

az rest --method post --uri /subscriptions/<AzureSubscriptionId>/resourceGroups/<ResourceGroupName> /providers/Microsoft.SaaS/resources/<SaaS Resource Name>/listAccessToken?api-version=2018-03-01-beta -o json

有关详细信息,请参阅此处 - Microsoft.SaaS 资源提供程序的规范。

使用 Terraform 从 Azure 市场部署 SaaS 产品/服务

查看上面的部分,介绍如何使用 ARM 部署 SaaS 产品/服务,因为 Terraform 部署将与使用 ARM 模板相同。

Azure 市场中的 Azure 应用程序

Azure 应用程序产品类型是唯一的产品/服务,使发布者能够创建一个 ARM 模板,其中包含一组捆绑并配置为提供功能齐全的多资源应用程序,Azure 应用程序有三种计划类型:

  • 解决方案模板 - 免费产品/服务、ARM 模板部署
  • 托管应用程序 - 免费或付费产品/服务,创建 Microsoft.Solutions/applications 资源类型

Azure 门户生成用于Azure 应用程序(托管应用程序)部署的 ARM 模板。 此 ARM 模板创建一个类型Microsoft.Solutions/applications资源,该资源指向特定计划,并从客户填写Azure 门户的 UI 字段中传入特定于应用程序的参数。

接受 Azure 托管应用条款

与虚拟机产品/服务一样,若要使用 ARM 模板以编程方式将 Azure 应用程序(托管应用程序)部署到 Azure 订阅中,订阅需要接受 Azure 托管应用计划的条款。 通过Azure 门户进行部署时,条款接受会隐式进行,以后在同一 Azure 订阅工作中以编程方式部署同一计划,且不会出现问题。

也可以使用与 VM 部分中所述的相同az vm image terms accept方式接受Azure 应用程序(托管应用程序)产品/服务的条款。

如果Azure 应用程序(托管应用程序)产品是付费产品(例如,它使用按月计费或按流量计费),则用于部署它的 Azure 订阅必须与有效的付款方式(例如,它不能是免费或赞助的订阅)相关联。

使用 ARM 模板和 Azure CLI 部署Azure 应用程序(托管应用程序)

下面是用于部署托管应用程序的 ARM 模板示例。

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "type": "string",
            "allowedValues": [
                "westus2",
                "westeurope",
                "eastus",
                "northeurope",
                "centralus",
                "eastus2",
                "francecentral",
                "uksouth"
            ]
        },
        "applicationResourceName": {
            "type": "string"
        },
        "managedResourceGroupId": {
            "type": "string",
            "defaultValue": ""
        },
        "managedIdentity": {
            "type": "object",
            "defaultValue": {}
        },
        "initialConsulVersion": {
            "type": "string",
            "defaultValue": "v1.11.2"
        },
        "storageAccountName": {
            "type": "string",
            "defaultValue": "[concat('storage', uniqueString(resourceGroup().id, deployment().name))]"
        },
        "blobContainerName": {
            "type": "string",
            "defaultValue": "[concat('blob', uniqueString(resourceGroup().id, deployment().name))]"
        },
        "identityName": {
            "type": "string",
            "defaultValue": "[concat(parameters('clusterName'), '-identity')]"
        },
        "clusterMode": {
            "type": "string",
            "defaultValue": "PRODUCTION",
            "allowedValues": [
                "PRODUCTION",
                "DEVELOPMENT"
            ]
        },
        "clusterName": {
            "type": "string",
            "defaultValue": "cluster"
        },
        "consulDataCenter": {
            "type": "string",
            "defaultValue": "dc1"
        },
        "numServers": {
            "type": "string",
            "defaultValue": "3"
        },
        "numServersDevelopment": {
            "type": "string",
            "defaultValue": "1"
        },
        "automaticUpgrades": {
            "type": "string",
            "defaultValue": "disabled"
        },
        "consulConnect": {
            "type": "string",
            "defaultValue": "enabled"
        },
        "externalEndpoint": {
            "type": "string",
            "defaultValue": "enabled"
        },
        "snapshotInterval": {
            "type": "string",
            "defaultValue": "1d"
        },
        "snapshotRetention": {
            "type": "string",
            "defaultValue": "1m"
        },
        "consulVnetCidr": {
            "type": "string",
            "defaultValue": "172.25.16.0/24"
        },
        "providerBaseURL": {
            "defaultValue": "https://ama-api.hashicorp.cloud/consulama/2021-04-23",
            "type": "String",
            "metadata": {
                "description": "The URI of the custom provider API"
            }
        },
        "email": {
            "type": "string"
        },
        "federationToken": {
            "type": "string",
            "defaultValue": ""
        },
        "sourceChannel": {
            "type": "string",
            "defaultValue": "azure-portal"
        },
        "auditLoggingEnabled": {
            "type": "string",
            "defaultValue": "disabled",
            "allowedValues": [
                "enabled",
                "disabled"
            ]
        },
        "auditLogStorageContainerURL": {
            "type": "string",
            "defaultValue": ""
        }
    },
    "variables": {
    },
    "resources": [
        {
            "type": "Microsoft.Solutions/applications",
            "apiVersion": "2017-09-01",
            "name": "[parameters('applicationResourceName')]",
            "location": "[parameters('location')]",
            "kind": "MarketPlace",
            "identity": "[if(empty(parameters('managedIdentity')),json('null'),parameters('managedIdentity'))]",
            "plan": {
                "name": "<Plan ID>",
                "product": "<Product ID>",
                "publisher": "<Publisher ID>",
                "version": "<Version>"
            },
            "properties": {
                "managedResourceGroupId": "[parameters('managedResourceGroupId')]",
                "parameters": {
                    "initialConsulVersion": {
                        "value": "[parameters('initialConsulVersion')]"
                    },
                    "storageAccountName": {
                        "value": "[parameters('storageAccountName')]"
                    },
                    "blobContainerName": {
                        "value": "[parameters('blobContainerName')]"
                    },
                    "identityName": {
                        "value": "[parameters('identityName')]"
                    },
                    "clusterMode": {
                        "value": "[parameters('clusterMode')]"
                    },
                    "clusterName": {
                        "value": "[parameters('clusterName')]"
                    },
                    "consulDataCenter": {
                        "value": "[parameters('consulDataCenter')]"
                    },
                    "numServers": {
                        "value": "[parameters('numServers')]"
                    },
                    "numServersDevelopment": {
                        "value": "[parameters('numServersDevelopment')]"
                    },
                    "automaticUpgrades": {
                        "value": "[parameters('automaticUpgrades')]"
                    },
                    "consulConnect": {
                        "value": "[parameters('consulConnect')]"
                    },
                    "externalEndpoint": {
                        "value": "[parameters('externalEndpoint')]"
                    },
                    "snapshotInterval": {
                        "value": "[parameters('snapshotInterval')]"
                    },
                    "snapshotRetention": {
                        "value": "[parameters('snapshotRetention')]"
                    },
                    "consulVnetCidr": {
                        "value": "[parameters('consulVnetCidr')]"
                    },
                    "location": {
                        "value": "[parameters('location')]"
                    },
                    "providerBaseURL": {
                        "value": "[parameters('providerBaseURL')]"
                    },
                    "email": {
                        "value": "[parameters('email')]"
                    },
                    "federationToken": {
                        "value": "[parameters('federationToken')]"
                    },
                    "sourceChannel": {
                        "value": "[parameters('sourceChannel')]"
                    },
                    "auditLoggingEnabled": {
                        "value": "[parameters('auditLoggingEnabled')]"
                    },
                    "auditLogStorageContainerURL": {
                        "value": "[parameters('auditLogStorageContainerURL')]"
                    }
                },
                "jitAccessPolicy": null
            }
        }
    ]
}

然后运行以下命令:

az group create --resource-group <Resource Group Name> --location <location>

az deployment group create --resource-group avmanagedapp100 --template-file <ARM Template JSON file> --parameters location=<location> applicationResourceName=<Resource Group Name> managedResourceGroupId=/subscriptions/<Subscription ID>/resourceGroups/<Resource Group Name>  email=<email address>

使用 Terraform 从 Azure 市场部署 Azure 托管应用

查看上述部分,介绍如何使用 ARM 部署 Azure 托管应用产品/服务,因为 Terraform 部署将使用相同的 ARM 模板。

来自Azure 市场的解决方案模板

从 Azure 市场 部署解决方案模板(而不是 Azure 托管应用)时,部署只是 ISV 发布的 ARM 模板,其中包含作为参数传递的相应 UI 字段。 若要以编程方式部署解决方案模板产品/服务,请使用Azure 门户执行部署、复制 ARM 模板,并将其用于后续部署。 由于解决方案模板不是“付费”产品/服务,因此无需接受任何特殊条款。 但是,如果解决方案模板 ARM 模板引用Azure 市场中的 VM 映像,则需要首先接受 VM 产品/服务的条款,如 VM 产品/服务所述。

后续步骤