以编程方式部署市场产品

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

先决条件

需要安装 Azure PowerShell 并连接到 Azure:

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

若要运行此示例,请安装最新版本的 Azure CLI。 若要启动,请运行

az login

以创建与 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 来接受条款。

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

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: 'aaaa0000-bb11-2222-33cc-444444dddddd' You have not accepted the legal terms on this subscription: 'aaaa0000-bb11-2222-33cc-444444dddddd' 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

有关如何使用 TerraformWindows VMLinux VM 部署虚拟机的说明。

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

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

  2. azurerm_virtual_machine 提供程序中指定 plan

注意

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

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 资源,因此,首次创建特定的 Marketplace VM 产品时,会创建一个独特的资源来表明法律条款被接受的事实。 但是,下次尝试在同一 Azure 订阅下部署另一个 VM(具有相同的发布者 ID 和服务 ID)时,会收到一条错误消息:

A resource with the ID "/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e /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 'aaaa0000-bb11-2222-33cc-444444dddddd' 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 应用产品类型是一个独特的产品/服务,它允许发布者创建一个包含一组 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 产品/服务的条款。