创建和部署 VM 应用程序

VM 应用程序是Azure计算库中的资源类型,可简化虚拟机的应用程序和脚本的发布、部署、管理、共享和全局分发。 VM 应用程序支持各种方案,包括大规模部署、低延迟要求、故障复原、安全受信任的推出、车队范围的一致性、微服务体系结构和部署后管理。 详细了解 VM 应用程序。

若要在 Azure VM 上创建和部署应用程序,请先打包应用程序并将其作为存储 Blob 上传到 Azure 存储帐户。 然后创建引用这些存储 Blob 的 Azure VM application 资源和 VM application version 资源。 Azure在区域托管存储帐户中存储和复制这些 Blob。 最后,通过在 applicationProfile 中传递应用程序引用,在任何 VM 或Virtual Machine Scale Sets上部署应用程序。

显示发布和部署 VM 应用程序所涉及的分步过程的关系图。

先决条件

  1. 创建 Azure 存储帐户 和 存储容器。 此容器用于上传应用程序文件。
  2. 创建 用于存储和共享应用程序资源的 Azure 计算库。
  3. 创建 用户分配的托管标识 并 分配给 Azure 计算资源库

注释

创建禁用匿名访问的存储帐户以提高安全性。 使用分配给 Azure 计算库的 托管标识安全地发布 VM 应用程序,而无需使用 SAS 令牌。

打包应用程序

在 Azure 计算库中发布 VM 应用程序资源之前,请将应用程序文件打包为上传到 Azure Storage 帐户。 下图显示了用于组织应用程序文件的可能文件夹结构:

显示用于上传和创建 VM 应用程序的文件夹结构的屏幕截图。

1.打包应用程序文件

VM 应用程序需要单个文件作为应用程序包。

  • 如果应用程序安装需要单个可执行文件(.exe、.msi、.sh、.ps等),则可以将其用作应用程序包。
  • 如果应用程序需要多个文件(可执行文件、依赖项、脚本、元数据等),请将它们存档到单个文件(.zip、 .tar、 .tar.gz)中,并将该文件用作应用程序包。
  • 对于微服务应用程序,可以将每个微服务打包并发布为单独的 Azure VM 应用程序。 这种模块化有助于使用 orderapplicationProfile 中的属性实现应用程序可重用性、跨团队开发和微服务的顺序安装。

详细了解 为 Azure VM 应用程序创建应用程序包。

2. (可选) 打包应用程序配置文件

  • 您可以使用defaultConfigurationLink在 VM 应用程序的publishingProfile中单独提供配置文件。 这减少了存档和取消存档应用程序包的开销。
  • 还可以在应用部署期间使用 configurationReference VM 中的 applicationProfile 属性传递配置文件,以便为每个 VM 启用自定义安装。
  • 如果设置了这两个属性,configurationReference 将覆盖通过 defaultConfigurationLink 传递的配置。

3.创建安装脚本

在 VM 上下载应用程序和配置 Blob 后,Azure 会执行提供的安装脚本来安装应用程序。 安装脚本以字符串形式提供 ,最大字符限制为 4,096 个字符。 如果应用程序包和配置文件位于当前目录中,则应编写安装命令。

安装脚本中可能需要执行少量操作。

  • (可选) 使用正确的命令解释器 Azure 使用的默认命令解释器位于 /bin/bash Linux OS 和 cmd.exe Windows OS 上。 如果计算机上安装了其他解释器(如 Chocolatey 或 PowerShell),可以使用其他解释器。 调用可执行文件并向其传递命令。 例如,powershell.exe -command '<powershell command>'。 如果使用的是 PowerShell,需要使用 3.11.0 版的 Az.Storage 模块。

  • (可选) 重命名应用程序 blob 和配置 blob Azure无法保留原始 Blob 名称和文件扩展名。 Azure下载应用程序文件,其中包含在Azure计算库上发布的 VM 应用程序资源的名称。 因此,如果 VM 应用的名称为“VMApp1”,则 VM 上下载的文件的名称为“VMApp1”,没有扩展名。 配置文件的默认名称为“VMApp1-config”,没有文件扩展名。

    您可以使用安装脚本将文件重命名,包括文件扩展名,或者您也可以在 VM 应用程序版本资源的 中传递名称到 和 属性中。 Azure下载文件时使用这些名称而不是默认名称。

  • 解除存档应用程序 Blob 对于已存档的应用程序包,在安装应用程序之前,需要将其解除存档。 使用 .zip 或.tar,因为大多数 OS 都内置支持取消存档这些格式。 对于其他格式,请确保客户操作系统已提供支持。

  • 将脚本转换为字符串 在 Azure VM 应用程序版本资源的publishingProfile中,安装脚本作为install属性的字符串进行传递。

  • 以静默和同步方式安装 在没有任何用户界面提示的情况下悄悄或无提示地安装应用程序。 由于安装在用户输入不可用的虚拟机(VM)上运行,您必须使用无提示或无人参与安装参数。 此外,如果应用程序安装程序生成后台或子进程或异步运行,则父脚本可能会在安装完成之前返回。 若要确保在Azure将操作标记为成功之前完成整个安装,请使用等待标志或阻止命令。 当安装涉及异步运行的多个步骤时,或者需要在后续应用程序 order 开始之前验证安装完成情况时,此方法会有所帮助。

    常见示例包括:

    • Windows .msi:start /wait msiexec /i app.msi /quiet /qn
    • Windows .exe:start /wait app.exe /silent 或 start /wait app.exe /quiet 或 start /wait app.exe /S
    • PowerShell:Start-Process -FilePath '.\app.exe' -ArgumentList '/quiet' -Wait
    • Linux .sh: ./install.sh --silent (默认情况下,shell 脚本同步运行)
    • Linux .deb: sudo DEBIAN_FRONTEND=noninteractive dpkg -i app.deb (默认情况下同步运行)
    • Linux .rpm: sudo rpm -ivh app.rpm (默认情况下同步运行)
  • (可选) 设置正确的执行策略和权限 取消存档后,可以重置文件权限。 在执行文件之前,最好设置正确的权限。

  • (可选) 设置详细执行和日志记录 VM 应用程序会自动捕获安装脚本的任何输出,并将其存储在 VM 上的 stdout 文件中。 使用此功能可以记录安装进度、调试失败并验证部署是否成功。

    • 启用详细模式 以记录应用程序安装程序日志。 Bash: set -x、PowerShell: $VerbosePreference='Continue'、Cmd: @echo on。
    • 添加自定义消息 以跟踪脚本进度。 Bash: echo "Install started"、PowerShell: Write-Output "Install started"、Cmd: echo Install started

    可以使用 “运行命令” 或连接到 VM 来检索这些日志。 文件stdout和stderr文件位于以下路径

    • Linux:/var/lib/waagent/Microsoft.CPlat.Core.VMApplicationManagerLinux/<application name>/<application version>/
    • Windows: C:\Packages\Plugins\Microsoft.CPlat.Core.VMApplicationManagerWindows\1.0.9\Downloads\<application name>\<application version>/
  • (可选) 将应用程序和配置 blob 移动到适当的位置 Azure 将应用程序 Blob 和配置 Blob 下载到以下位置。 安装脚本必须在必要时将文件移动到适当的位置。

    Linux:/var/lib/waagent/Microsoft.CPlat.Core.VMApplicationManagerLinux/<application name>/<application version>

    Windows: C:\Packages\Plugins\Microsoft.CPlat.Core.VMApplicationManagerWindows\1.0.16\Downloads\<application name>\<application version>

下面是几个基于应用程序 Blob 文件扩展名的示例安装脚本。 如果在publishingProfile中以文件名和扩展名形式提供packageFileName 和 configFileName 属性,请删除以下脚本中用于重命名文件的代码。

将 VMApp1 替换为应用程序包(VMApp1)和配置文件(VMApp1-config)中的 VM 应用名称。 将 app.tar 替换为所需的应用名称。 替换 config.yaml 为您所需的配置名称。

在不使用配置文件的情况下使用 bash 进行安装:

mv VMApp1 app.tar && tar -xf app.tar && chmod -R +xr . && bash ./install.sh
mv VMApp1 app.tar && tar -xf app.tar && chmod -R +xr . && sudo DEBIAN_FRONTEND=noninteractive dpkg -i ./app.deb

通过配置文件使用 bash 进行安装:

mv VMApp1 app.tar && mv VMApp1-config config.yaml && tar -xf app.tar && chmod -R +xr . && bash ./install.sh --config config.yaml

如果配置文件打包在 tar 文件中

mv VMApp1 app.tar && tar -xf app.tar && chmod -R +xr . && sudo debconf-set-selections < config.cfg && sudo DEBIAN_FRONTEND=noninteractive dpkg -i ./app.deb

4.创建删除脚本

通过该 remove 脚本,可以定义用于删除应用程序的操作。 该 remove 脚本以字符串形式提供,最大字符限制为 4,096 个字符。 编写假设应用程序包和配置文件位于当前目录中的命令。 在卸载作期间,Azure运行卸载脚本,然后从存储库中删除所有文件。

脚本必须执行的操作很少 remove 。

  • 卸载应用程序: 从 VM 正确卸载应用程序。 例如:

Windows上的 CMD:

start /wait uninstall.exe /quiet
start /wait python.exe /uninstall /quiet
start /wait msiexec /x app.msi /quiet /qn
  • 删除残差文件: 如果应用程序安装在文件系统的其他部分创建文件,请删除这些文件。

将应用程序文件上传到 Azure 存储帐户

重要

在上传文件或生成 SAS URL 之前,请将角色分配给对目标存储帐户或容器执行这些作的用户、服务主体或托管标识:

  • 存储 Blob 数据贡献者或存储 Blob 数据所有者:所需权限用于上传、修改或删除 Blob。
  • 若要使用 Azure AD 生成 SAS(例如,在 CLI/PowerShell 中使用 --auth-mode 登录名),请在存储帐户范围内分配存储 Blob 委派器。

1. 将您的应用程序和配置文件上传到位于Azure存储帐户中的容器。

将打包的应用程序和可选配置文件作为 Blob 上传。 块 Blob 适用于大多数方案。

(可选)如果需要使用页 Blob,请在使用以下脚本之一上传之前对文件进行字节对齐:

inputFile="<the file you want to pad>"

# Get the file size
fileSize=$(stat -c %s "$inputFile")

# Calculate the remainder when divided by 512
remainder=$((fileSize % 512))

if [ "$remainder" -ne 0 ]; then
  # Calculate how many bytes to pad
  difference=$((512 - remainder))
  
  # Create padding (empty bytes)
  dd if=/dev/zero bs=1 count=$difference >> "$inputFile"
fi

2. 为应用程序包和配置文件生成 SAS 或 Blob URL

将应用程序和配置文件上传到存储帐户后,需要为这些 Blob 生成具有读取权限的 Blob URL 或 SAS URL 。 然后,在创建 VM 应用程序版本资源时提供这些 URL 作为参考。

注释

强烈建议将 Blob URL 与托管标识配合使用,以提高安全性和隐私性。

使用 Blob URL:

使用 SAS URL:

  • 如果存储帐户被禁用匿名访问,并且未将托管标识分配给 Azure 计算画廊。

Blob URL = https://${STORAGE_ACCOUNT}.blob.core.chinacloudapi.cn/${CONTAINER_NAME}/${BLOB_NAME} SAS URL = https://${STORAGE_ACCOUNT}.blob.core.chinacloudapi.cn/${CONTAINER_NAME}/${BLOB_NAME}?${SAS_TOKEN}

如果为 Azure Compute Gallery 分配了托管标识,请使用以下脚本。 此方法使用不使用 SAS 令牌的 Blob URL。

#!/bin/bash
set -euo pipefail

# === CONFIGURATION ===
STORAGE_ACCOUNT="your-storage-account-name"
CONTAINER_NAME="your-container-name"
APP_FILE="./path/to/your-app-file"           # Path to your application payload file        
CONFIG_FILE="./path/to/your-config-file"     # Path to your configuration file (optional)   

# === LOGIN (if not already logged in) ===
az cloud set -n AzureChinaCloud
az login --only-show-errors

# === CREATE CONTAINER IF NOT EXISTS ===
az storage container create \
  --name "$CONTAINER_NAME" \
  --account-name "$STORAGE_ACCOUNT" \
  --auth-mode login \
  --only-show-errors

# === UPLOAD APPLICATION FILE ===
APP_BLOB_NAME=$(basename "$APP_FILE")
az storage blob upload \
  --account-name "$STORAGE_ACCOUNT" \
  --container-name "$CONTAINER_NAME" \
  --name "$APP_BLOB_NAME" \
  --file "$APP_FILE" \
  --auth-mode login \
  --overwrite \
  --only-show-errors

# === UPLOAD CONFIG FILE (optional) ===
if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
  CONFIG_BLOB_NAME=$(basename "$CONFIG_FILE")
  az storage blob upload \
    --account-name "$STORAGE_ACCOUNT" \
    --container-name "$CONTAINER_NAME" \
    --name "$CONFIG_BLOB_NAME" \
    --file "$CONFIG_FILE" \
    --auth-mode login \
    --overwrite \
    --only-show-errors
fi

# === GENERATE BLOB URLs ===
echo "Generating Blob URLs..."

APP_BLOB_URL="https://${STORAGE_ACCOUNT}.blob.core.chinacloudapi.cn/${CONTAINER_NAME}/${APP_BLOB_NAME}"
echo "Application file: $APP_BLOB_URL"

if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
  CONFIG_BLOB_URL="https://${STORAGE_ACCOUNT}.blob.core.chinacloudapi.cn/${CONTAINER_NAME}/${CONFIG_BLOB_NAME}"
  echo "Configuration file: $CONFIG_BLOB_URL"
fi

创建 VM 应用程序

若要创建 VM 应用程序,请先创建描述应用程序的 VM 应用程序 资源。 然后在其中创建 VM 应用程序版本 资源,其中包含用于安装、更新和删除应用程序的 VM 应用程序有效负载和脚本。 负载内容使用 Blob URL 或 SAS URL 提供给 Azure 存储帐户中的 blob 容器。

请参阅 VM 应用程序和 VM 应用程序版本资源的架构 ,详细了解每个属性。

使用以下 ARM 模板创建 Azure Compute Gallery、VM 应用程序和 VM 应用程序版本。 此模板演示用于发布应用程序包的关键属性和配置选项。

{
  "$schema": "https://schema.management.azure.com/schemas/2020-06-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "galleryName": {
      "type": "string"
    },
    "applicationName": {
      "type": "string"
    },
    "versionName": {
      "type": "string",
      "metadata": {
        "description": "Must follow the format: major.minor.patch (Example: 1.0.0)"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "China North 2"
    },
    "supportedOSType": {
      "type": "string",
      "allowedValues": ["Windows", "Linux"]
    },
    "endOfLifeDate": {
      "type": "string",
      "metadata": {
        "description": "Optional. This property is for information only and doesn't block app deployment."
      }
    },
    "description": {
      "type": "string",
      "defaultValue": "Description of the application"
    },
    "eula": {
      "type": "string",
      "defaultValue": ""
    },
    "privacyStatementUri": {
      "type": "string",
      "defaultValue": ""
    },
    "releaseNoteUri": {
      "type": "string",
      "defaultValue": ""
    },
    "mediaLink": {
      "type": "string"
    },
    "configLink": {
      "type": "string"
    },
    "appConfigFileName": {
      "type": "string"
    },
    "appPackageFileName": {
      "type": "string"
    },
    "replicaRegion1": {
      "type": "string",
      "defaultValue": "China North 2"
    },
    "replicaRegion2": {
      "type": "string",
      "defaultValue": "China North 2"
    },
    "installScript": {
      "type": "string",
      "metadata": {
        "description": "Optional. Script to run to install the application. Example: echo 'Installing application...'"
      }
    },
    "updateScript": {
      "type": "string",
      "metadata": {
        "description": "Optional. Script to run to update the application. Example: echo 'Updating application...'"
      }
    },
    "removeScript": {
      "type": "string",
      "metadata": {
        "description": "Optional. Script to run to delete the application. Example: echo 'Deleting application...'"
      }
    },
    "storageAccountType": {
      "type": "string",
      "allowedValues": ["PremiumV2_LRS", "Premium_LRS", "Standard_LRS", "Standard_ZRS"],
      "defaultValue": "Standard_LRS"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Compute/galleries",
      "apiVersion": "2024-03-03",
      "name": "[parameters('galleryName')]",
      "location": "[parameters('location')]",
      "properties": {
        "identifier": {}
      }
    },
    {
      "type": "Microsoft.Compute/galleries/applications",
      "apiVersion": "2024-03-03",
      "name": "[format('{0}/{1}', parameters('galleryName'), parameters('applicationName'))]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Compute/galleries', parameters('galleryName'))]"
      ],
      "properties": {
        "supportedOSType": "[parameters('supportedOSType')]",
        "endOfLifeDate": "[parameters('endOfLifeDate')]",
        "description": "[parameters('description')]",
        "eula": "[if(equals(parameters('eula'), ''), json('null'), parameters('eula'))]",
        "privacyStatementUri": "[if(equals(parameters('privacyStatementUri'), ''), json('null'), parameters('privacyStatementUri'))]",
        "releaseNoteUri": "[if(equals(parameters('releaseNoteUri'), ''), json('null'), parameters('releaseNoteUri'))]"
      }
    },
    {
      "type": "Microsoft.Compute/galleries/applications/versions",
      "apiVersion": "2024-03-03",
      "name": "[format('{0}/{1}/{2}', parameters('galleryName'), parameters('applicationName'), parameters('versionName'))]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Compute/galleries/applications', parameters('galleryName'), parameters('applicationName'))]"
      ],
      "properties": {
        "publishingProfile": {
          "source": {
            "mediaLink": "[parameters('mediaLink')]",
            "defaultConfigurationLink": "[parameters('configLink')]"
          },
          "manageActions": {
            "install": "[parameters('installScript')]",
            "remove": "[parameters('removeScript')]",
            "update": "[parameters('updateScript')]"
          },
          "settings": {
            "scriptBehaviorAfterReboot": "Rerun",
            "configFileName": "[parameters('appConfigFileName')]",
            "packageFileName": "[parameters('appPackageFileName')]"
          },
          "targetRegions": [
            {
              "name": "[parameters('location')]",
              "regionalReplicaCount": 3,
              "storageAccountType": "[parameters('storageAccountType')]"
            },
            {
              "name": "[parameters('replicaRegion1')]",
              "regionalReplicaCount": 1,
              "storageAccountType": "[parameters('storageAccountType')]"
            },
            {
              "name": "[parameters('replicaRegion2')]"
            },
          ],
          "excludeFromLatest": false,
          "replicaCount": 2,
          "storageAccountType": "[parameters('storageAccountType')]"
        },
        "safetyProfile": {
          "allowDeletionOfReplicatedLocations": true
        },
        "endOfLifeDate": "[parameters('endOfLifeDate')]"
      }
    }
  ]
}

部署 VM 应用

可以在 Azure 虚拟机或 Azure 虚拟机规模集的 applicationProfile 中引用一个或多个 VM 应用程序。 然后,Azure 会拉取 VM 应用程序的有效负载,并使用提供的安装脚本在每个 VM 上安装它。 该 order 属性定义 VM 应用程序在 VM 上安装的顺序。

请参阅VM / Virtual Machine Scale Sets 的 applicationProfile 的 schema以了解每个属性的详情。

使用以下 ARM 模板在 Azure VM 或Azure Virtual Machine Scale Sets上部署 VM 应用程序。 此模板演示用于部署 VM 应用程序的关键属性和配置选项。

在虚拟机规模集中部署虚拟机应用程序

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmssName": {
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "subscriptionId": {
      "type": "string"
    },
    "resourceGroupName": {
      "type": "string"
    },
    "galleryName": {
      "type": "string"
    },
    "applicationName1": {
      "type": "string"
    },
    "applicationVersion1": {
      "type": "string",
      "defaultValue": "latest"
    },
    "configurationReference1": {
      "type": "string",
      "metadata": {
        "description": "Optional path to configuration file from Storage Account. Overrides default configuration file."
      }
    },
    "applicationName2": {
      "type": "string"
    },
    "applicationVersion2": {
      "type": "string",
      "defaultValue": "latest"
    }
  },
  "variables": {
    "packageReferenceId1": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/galleries/{2}/applications/{3}/versions/{4}', parameters('subscriptionId'), parameters('resourceGroupName'), parameters('galleryName'), parameters('applicationName1'), parameters('applicationVersion1'))]",
    "packageReferenceId2": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/galleries/{2}/applications/{3}/versions/{4}', parameters('subscriptionId'), parameters('resourceGroupName'), parameters('galleryName'), parameters('applicationName2'), parameters('applicationVersion2'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Compute/virtualMachineScaleSets",
      "apiVersion": "2024-03-03",
      "name": "[parameters('vmssName')]",
      "location": "[parameters('location')]",
      "properties": {
        "virtualMachineProfile": {
          "applicationProfile": {
            "galleryApplications": [
              {
                "order": 1,
                "packageReferenceId": "[variables('packageReferenceId1')]",
                "configurationReference": "[parameters('configurationReference1')]",
                "treatFailureAsDeploymentFailure": true
              },
              {
                "order": 2,
                "packageReferenceId": "[variables('packageReferenceId2')]",
                "treatFailureAsDeploymentFailure": false
              }
            ]
          }
        }
      }
    }
  ]
}

在 Azure VM 上部署 VM 应用程序

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmName": {
      "type": "string"
    },
    "location": {
      "type": "string"
    },
    "subscriptionId": {
      "type": "string"
    },
    "resourceGroupName": {
      "type": "string"
    },
    "galleryName": {
      "type": "string"
    },
    "applicationName1": {
      "type": "string"
    },
    "applicationVersion1": {
      "type": "string",
      "defaultValue": "latest"
    },
    "configurationReference1": {
      "type": "string",
      "metadata": {
        "description": "Optional path to configuration blob for application 1"
      }
    },
    "applicationName2": {
      "type": "string"
    },
    "applicationVersion2": {
      "type": "string",
      "defaultValue": "latest"
    }
  },
  "variables": {
    "packageReferenceId1": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/galleries/{2}/applications/{3}/versions/{4}', parameters('subscriptionId'), parameters('resourceGroupName'), parameters('galleryName'), parameters('applicationName1'), parameters('applicationVersion1'))]",
    "packageReferenceId2": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/galleries/{2}/applications/{3}/versions/{4}', parameters('subscriptionId'), parameters('resourceGroupName'), parameters('galleryName'), parameters('applicationName2'), parameters('applicationVersion2'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2024-07-01",
      "name": "[parameters('vmName')]",
      "location": "[parameters('location')]",
      "properties": {
        "applicationProfile": {
          "galleryApplications": [
            {
              "order": 1,
              "packageReferenceId": "[variables('packageReferenceId1')]",
              "configurationReference": "[parameters('configurationReference1')]",
              "treatFailureAsDeploymentFailure": true
            },
            {
              "order": 2,
              "packageReferenceId": "[variables('packageReferenceId2')]",
              "treatFailureAsDeploymentFailure": false
            }
          ]
        }
      }
    }
  ]
}

后续步骤