你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 Linux 虚拟机上使用 Azure 自定义脚本扩展版本 2

自定义脚本扩展版本 2 在 Azure 虚拟机 (VM) 上下载和运行脚本。 此扩展适用于部署后配置、软件安装或其他任何配置或管理任务。 可以从 Azure 存储或其他可访问的 Internet 位置下载脚本,或者将脚本提供给扩展运行时。

将自定义脚本扩展与 Azure 资源管理器模板集成。 也可使用 Azure CLI、PowerShell 或 Azure 虚拟机 REST API 来运行它。

本文详细介绍如何使用 Azure CLI 中的自定义脚本扩展,以及如何使用 Azure 资源管理器模板来运行该扩展。 本文还提供针对 Linux 系统的疑难解答步骤。

存在两个 Linux 自定义脚本扩展:

  • 版本 1:Microsoft.OSTCExtensions.CustomScriptForLinux
  • 版本 2:Microsoft.Azure.Extensions.CustomScript

请将新部署和现有部署切换为使用版本 2。 新版本是一种普适性替换。 迁移与更改名称和版本一样简单。 你无需更改扩展配置。

先决条件

操作系统

适用于 Linux 的自定义脚本扩展将在支持的操作系统上运行。 有关详细信息,请参阅 Azure 上认可的 Linux 发行版

脚本位置

可以将该扩展配置为使用 Azure Blob 存储凭据来访问 Azure Blob 存储。 脚本位置可以是任何位置,只要 VM 可以路由到该终结点(例如 GitHub 或内部文件服务器)即可。

Internet 连接

如果需要从外部(例如 GitHub 或 Azure 存储)下载脚本,则需要打开其他防火墙和网络安全组 (NSG) 端口。 例如,如果脚本位于 Azure 存储中,可以使用 Azure NSG 存储服务标记来允许访问。

如果脚本位于本地服务器上,可能仍然需要打开其他防火墙或 NSG 端口。

提示和技巧

  • 此扩展的最高失败率是由于脚本中的语法错误。 测试脚本是否运行且没有错误。 将其他日志记录放入脚本,以便更轻松地查找失败。
  • 编写幂等脚本,确保意外多次运行这些脚本不会导致系统更改。
  • 确保在运行这些脚本时不需要用户输入。
  • 脚本允许运行 90 分钟。 任何更长时间都将导致该扩展预配失败。
  • 不要将重新启动置于脚本内。 此操作会导致正在安装的其他扩展出现问题,并且重新启动后该扩展不会继续工作。
  • 如果脚本会导致在安装应用程序和运行脚本之前重新启动,则使用 Cron 作业或 DSC、Chef 或 Puppet 扩展等工具计划重启。
  • 建议不要运行会导致 VM 代理停止或更新的脚本。 这可能会使该扩展处于“正在转换”状态,导致超时。
  • 该扩展只运行一次脚本。 如果想要在每次启动时运行脚本,则可以使用 cloud-init 映像Scripts Per Boot 模块。 或者,可以使用脚本创建 systemd 服务单元。
  • 只能向 VM 应用一个扩展版本。 若要运行另一个自定义脚本,可以使用新配置更新现有扩展。 也可以删除自定义脚本扩展,然后使用已更新的脚本重新应用该扩展。
  • 如果想要计划脚本何时运行,应使用该扩展创建一个 Cron 作业。
  • 脚本运行时,Azure 门户或 CLI 中只会显示“正在转换”扩展状态。 如果希望更频繁地更新正在运行的脚本的状态,需要创建自己的解决方案。
  • 自定义脚本扩展不能原生支持代理服务器。 但是,可以使用支持脚本中的代理服务器的文件传输工具,例如 Curl
  • 请注意脚本或命令可能依赖的非默认目录位置。 将按逻辑对其进行处理。

扩展架构

自定义脚本扩展配置指定脚本位置和要运行命令等设置。 可将此信息存储在配置文件中、在命令行中指定,或者在 Azure 资源管理器模板中指定。

可将敏感数据存储在受保护的配置中,此配置经过加密,只能在目标虚拟机上解密。 当执行命令包含机密(例如密码)时,受保护的配置相当有用。 下面是一个示例:

{
  "name": "config-app",
  "type": "Extensions",
  "location": "[resourceGroup().location]",
  "apiVersion": "2019-03-01",
  "dependsOn": [
    "[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmName'),copyindex()))]"
  ],
  "tags": {
    "displayName": "config-app"
  },
  "properties": {
    "publisher": "Microsoft.Azure.Extensions",
    "type": "CustomScript",
    "typeHandlerVersion": "2.1",
    "autoUpgradeMinorVersion": true,
    "settings": {
      "skipDos2Unix":false,
      "timestamp":123456789          
    },
    "protectedSettings": {
       "commandToExecute": "<command-to-execute>",
       "script": "<base64-script-to-execute>",
       "storageAccountName": "<storage-account-name>",
       "storageAccountKey": "<storage-account-key>",
       "fileUris": ["https://.."],
       "managedIdentity" : "<managed-identity-identifier>"
    }
  }
}

注意

managedIdentity 属性不能与 storageAccountNamestorageAccountKey 属性结合使用。

属性值

名称 值或示例 数据类型
apiVersion 2019-03-01 date
publisher Microsoft.Azure.Extensions string
type CustomScript string
typeHandlerVersion 2.1 int
fileUris https://github.com/MyProject/Archive/MyPythonScript.py array
commandToExecute python MyPythonScript.py \<my-param1> string
script IyEvYmluL3NoCmVjaG8gIlVwZGF0aW5nIHBhY2thZ2VzIC4uLiIKYXB0IHVwZGF0ZQphcHQgdXBncmFkZSAteQo= string
skipDos2Unix false 布尔
timestamp 123456789 32-bit integer
storageAccountName examplestorageacct string
storageAccountKey TmJK/1N3AbAZ3q/+hOXoi/l73zOqsaxXDhqa9Y83/v5UpXQp2DQIBuv2Tifp60cE/OaHsJZmQZ7teQfczQj8hg== string
managedIdentity { }{ "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232" }{ "objectId": "12dd289c-0583-46e5-b9b4-115d5c19ef4b" } JSON 对象

属性值详细信息

属性 可选或必需 详细信息
apiVersion 不适用 可以使用资源浏览器或 Azure CLI 中的 az provider list -o json 命令查找最新的 API 版本。
fileUris 可选 要下载的文件的 URL。
commandToExecute 如果未设置 script,则为必需项 要运行的入口点脚本。 如果命令包含机密(例如密码),请使用此属性而不使用 script
script 如果未设置 commandToExecute,则为必需项 /bin/sh 运行的 Base64 编码(可选 gzip)脚本。
skipDos2Unix 可选 如果要跳过基于脚本的文件 URL 或脚本的 dos2unix 转换,请将此值设置为 false
timestamp 可选 仅更改此值以触发脚本的重新运行。 任何整数值都是可接受的,只要它与之前的值不同。
storageAccountName 可选 存储帐户的名称。 如果指定存储凭据,则所有 fileUris 值都必须是 Azure blob 的 URL。
storageAccountKey 可选 存储帐户的访问密钥。
managedIdentity 可选 用于下载文件的托管标识

clientId(可选,字符串):托管标识的客户端 ID。

objectId(可选,字符串):托管标识的对象 ID。

可以在公共设置或受保护设置中设置以下值。 该扩展将拒绝在公共和受保护设置中设置这些值的任何配置。

  • commandToExecute
  • script
  • fileUris

虽然使用公共设置可能对调试很有用,但强烈建议使用受保护设置。

公共设置会以明文形式发送到将运行脚本的 VM。 受保护设置使用只有 Azure 和 VM 知道的密钥进行加密。 这些设置在发送时保存到 VM。 也就是说,如果设置已加密,它们会以加密方式保存在 VM 上。 用于解密加密值的证书存储在 VM 上。 该证书还用于在运行时解密设置(如有必要)。

属性:skipDos2Unix

默认值为 false,这意味着执行 dos2unix 转换。

旧版自定义脚本扩展 Microsoft.OSTCExtensions.CustomScriptForLinux 会通过将 \r\n 转换为 \n 来自动将 DOS 文件转换为 UNIX 文件。 此转换仍然存在,并且默认为启用状态。 此转换适用于从 fileUris 下载的所有文件或基于任何下述标准的脚本设置:

  • 扩展名为 .sh、.txt、.py 或 .pl。 脚本设置将始终匹配此标准,因为它假定是使用 /bin/sh 运行的脚本。 脚本设置在 VM 上另存为 script.sh。
  • 文件以 #! 开头。

可以通过将 skipDos2Unix 设置为 true 来跳过 dos2unix 转换:

{
  "fileUris": ["<url>"],
  "commandToExecute": "<command-to-execute>",
  "skipDos2Unix": true
}

属性:脚本

自定义脚本扩展支持执行用户定义的脚本。 脚本设置将 commandToExecutefileUris 合并到单个设置中。 可以直接将脚本编码为设置,不必设置一个需要从 Azure 存储或 GitHub Gist 下载的文件。 可以使用脚本替换 commandToExecutefileUris

下面是一些要求:

  • 脚本必须进行 base64 编码。
  • 可以选择对脚本执行 gzip 操作。
  • 可以在公共或受保护设置中使用脚本设置。
  • 脚本参数数据的最大大小为 256 KB。 如果脚本超过此大小,它将不会运行。

例如,以下脚本保存到 /script.sh/ 文件:

#!/bin/sh
echo "Updating packages ..."
apt update
apt upgrade -y

你将通过采用以下命令的输出来构造正确的自定义脚本扩展脚本设置:

cat script.sh | base64 -w0
{
  "script": "IyEvYmluL3NoCmVjaG8gIlVwZGF0aW5nIHBhY2thZ2VzIC4uLiIKYXB0IHVwZGF0ZQphcHQgdXBncmFkZSAteQo="
}

在大多数情况下,可以选择对脚本进行 gzip 压缩以进一步减小大小。 自定义脚本扩展会自动检测 gzip 压缩的使用。

cat script | gzip -9 | base64 -w 0

自定义脚本扩展使用以下算法来运行脚本:

  1. 断言脚本值的长度不超过 256 KB。
  2. Base64 对脚本的值进行解码。
  3. 尝试对 Base64 解码的值执行 gunzip 操作。
  4. 将解码(以及可以选择进行解压缩)的值写入磁盘 (/var/lib/waagent/custom-script/#/script.sh)。
  5. 使用 _/bin/sh -c /var/lib/waagent/custom-script/#/script.sh 运行脚本。

属性:managedIdentity

注意

此属性只能在受保护的设置中指定。

自定义脚本扩展(2.1 及更高版本)支持托管标识,以便从 fileUris 设置中提供的 URL 下载文件。 它允许自定义脚本扩展访问 Azure 存储私有 blob 或容器,而无需用户传递共享访问签名 (SAS) 令牌或存储帐户密钥等机密。

要使用此功能,用户必须将系统分配的标识用户分配的标识添加到预计会运行自定义脚本扩展的 VM 或虚拟机规模集。 然后,用户必须授予托管标识访问 Azure 存储容器或 blob 的权限

要在目标 VM 或虚拟机规模集上使用系统分配的标识,请将 managedidentity 设置为空 JSON 对象。

示例:

{
  "fileUris": ["https://mystorage.blob.core.windows.net/privatecontainer/script1.sh"],
  "commandToExecute": "sh script1.sh",
  "managedIdentity" : {}
}

要在目标 VM 或虚拟机规模集上使用用户分配的标识,请使用托管标识的客户端 ID 或对象 ID 配置 managedidentity 字段。

示例:

{
  "fileUris": ["https://mystorage.blob.core.windows.net/privatecontainer/script1.sh"],
  "commandToExecute": "sh script1.sh",
  "managedIdentity" : { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232" }
}
{
  "fileUris": ["https://mystorage.blob.core.windows.net/privatecontainer/script1.sh"],
  "commandToExecute": "sh script1.sh",
  "managedIdentity" : { "objectId": "12dd289c-0583-46e5-b9b4-115d5c19ef4b" }
}

注意

managedIdentity 属性不能与 storageAccountNamestorageAccountKey 属性结合使用。

模板部署

可使用 Azure 资源管理器模板部署 Azure VM 扩展。 可以在 Azure 资源管理器模板中使用上一部分中详细介绍的 JSON 架构,以便在部署过程中运行自定义脚本扩展。 可在 GitHub 上找到包含自定义脚本扩展的示例模板。

{
  "name": "config-app",
  "type": "extensions",
  "location": "[resourceGroup().location]",
  "apiVersion": "2019-03-01",
  "dependsOn": [
    "[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmName'),copyindex()))]"
  ],
  "tags": {
    "displayName": "config-app"
  },
  "properties": {
    "publisher": "Microsoft.Azure.Extensions",
    "type": "CustomScript",
    "typeHandlerVersion": "2.1",
    "autoUpgradeMinorVersion": true,
    "settings": {
      },
    "protectedSettings": {
      "commandToExecute": "sh hello.sh <param2>",
      "fileUris": ["https://github.com/MyProject/Archive/hello.sh"
      ]  
    }
  }
}

注意

这些属性名称区分大小写。 要避免部署问题,请使用如下所示的名称。

Azure CLI

使用 Azure CLI 运行自定义脚本扩展时,请创建一个或多个配置文件。 至少必须具有 commandToExecute

az vm extension set \
  --resource-group myResourceGroup \
  --vm-name myVM --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --protected-settings ./script-config.json

(可选)可以在命令中以 JSON 格式字符串形式指定设置。 这样,便可以在执行期间指定配置,而无需使用单独的配置文件。

az vm extension set \
  --resource-group exttest \
  --vm-name exttest \
  --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --protected-settings '{"fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"],"commandToExecute": "./config-music.sh"}'

示例:包含脚本文件的公共配置

{
  "fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"],
  "commandToExecute": "./config-music.sh"
}

Azure CLI 命令:

az vm extension set \
  --resource-group myResourceGroup \
  --vm-name myVM --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings ./script-config.json

示例:不包含脚本文件的公共配置

{
  "commandToExecute": "apt-get -y update && apt-get install -y apache2"
}

Azure CLI 命令:

az vm extension set \
  --resource-group myResourceGroup \
  --vm-name myVM --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings ./script-config.json

示例:公共和受保护配置文件

使用公共配置文件来指定脚本文件的 URI。 使用受保护的配置文件来指定要运行的命令。

公共配置文件:

{
  "fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"]
}

受保护的配置文件:

{
  "commandToExecute": "./config-music.sh <param1>"
}

Azure CLI 命令:

az vm extension set \
  --resource-group myResourceGroup \
  --vm-name myVM \ 
  --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings ./script-config.json \
  --protected-settings ./protected-config.json

虚拟机规模集

如果从 Azure 门户部署自定义脚本扩展,则不能控制用于访问存储帐户中的脚本的 SAS 令牌的过期时间。 结果是初始部署正常,但当存储帐户的 SAS 令牌过期时,任何后续的缩放操作都将失败,因为自定义脚本扩展不能再访问存储帐户。

在虚拟机规模集上部署自定义脚本扩展时,建议使用 PowerShellAzure CLIAzure 资源管理器模板。 这样,你可以选择使用托管标识,或者直接控制 SAS 令牌的过期时间,以便根据需要随时访问存储帐户中的脚本。

故障排除

运行自定义脚本扩展时,会创建脚本,或将脚本下载到类似于以下示例的目录中。 命令输出也会保存到此目录中的 stdoutstderr 文件中。

/var/lib/waagent/custom-script/download/0/

要排除故障,请首先查看 Linux 代理日志,确保扩展运行:

/var/log/waagent.log 

查找扩展执行。 它将如下所示:

2018/04/26 17:47:22.110231 INFO [Microsoft.Azure.Extensions.customScript-2.0.6] [Enable] current handler state is: notinstalled
2018/04/26 17:47:22.306407 INFO Event: name=Microsoft.Azure.Extensions.customScript, op=Download, message=Download succeeded, duration=167
2018/04/26 17:47:22.339958 INFO [Microsoft.Azure.Extensions.customScript-2.0.6] Initialize extension directory
2018/04/26 17:47:22.368293 INFO [Microsoft.Azure.Extensions.customScript-2.0.6] Update settings file: 0.settings
2018/04/26 17:47:22.394482 INFO [Microsoft.Azure.Extensions.customScript-2.0.6] Install extension [bin/custom-script-shim install]
2018/04/26 17:47:23.432774 INFO Event: name=Microsoft.Azure.Extensions.customScript, op=Install, message=Launch command succeeded: bin/custom-script-shim install, duration=1007
2018/04/26 17:47:23.476151 INFO [Microsoft.Azure.Extensions.customScript-2.0.6] Enable extension [bin/custom-script-shim enable]
2018/04/26 17:47:24.516444 INFO Event: name=Microsoft.Azure.Extensions.customScript, op=Enable, message=Launch command succeeded: bin/custom-sc

在以上脚本中:

  • Enable 表示该命令何时开始运行。
  • Download 涉及下载 Azure 中的自定义脚本扩展包,而非 fileUris 中指定的脚本文件。

Azure 脚本扩展生成一个日志,位置如下:

/var/log/azure/custom-script/handler.log

查找单个执行。 它将如下所示:

time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event=start
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event=pre-check
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="comparing seqnum" path=mrseq
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="seqnum saved" path=mrseq
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="reading configuration"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="read configuration"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="validating json schema"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="json schema valid"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="parsing configuration json"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="parsed configuration json"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="validating configuration logically"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="validated configuration"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="creating output directory" path=/var/lib/waagent/custom-script/download/0
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="created output directory"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 files=1
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 file=0 event="download start"
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 file=0 event="download complete" output=/var/lib/waagent/custom-script/download/0
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="executing command" output=/var/lib/waagent/custom-script/download/0
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="executing protected commandToExecute" output=/var/lib/waagent/custom-script/download/0
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event="executed command" output=/var/lib/waagent/custom-script/download/0
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event=enabled
time=2018-04-26T17:47:23Z version=v2.0.6/git@1008306-clean operation=enable seq=0 event=end

可在此处看到:

  • 启动此日志的 enable 命令。
  • 传递给扩展的设置。
  • 下载文件的扩展及其结果。
  • 正在运行的命令及结果。

还可以使用 Azure CLI 检索自定义脚本扩展的执行状态,包括作为 commandToExecute 传递的实际参数:

az vm extension list -g myResourceGroup --vm-name myVM

输出类似于以下文本:

[
  {
    "autoUpgradeMinorVersion": true,
    "forceUpdateTag": null,
    "id": "/subscriptions/subscriptionid/resourceGroups/rgname/providers/Microsoft.Compute/virtualMachines/vmname/extensions/customscript",
    "resourceGroup": "rgname",
    "settings": {
      "commandToExecute": "sh script.sh > ",
      "fileUris": [
        "https://catalogartifact.azureedge.net/publicartifacts/scripts/script.sh",
        "https://catalogartifact.azureedge.net/publicartifacts/scripts/script.sh"
      ]
    },
    "tags": null,
    "type": "Microsoft.Compute/virtualMachines/extensions",
    "typeHandlerVersion": "2.0",
    "virtualMachineExtensionType": "CustomScript"
  },
  {
    "autoUpgradeMinorVersion": true,
    "forceUpdateTag": null,
    "id": "/subscriptions/subscriptionid/resourceGroups/rgname/providers/Microsoft.Compute/virtualMachines/vmname/extensions/OmsAgentForLinux",
    "instanceView": null,
    "location": "eastus",
    "name": "OmsAgentForLinux",
    "protectedSettings": null,
    "provisioningState": "Succeeded",
    "publisher": "Microsoft.EnterpriseCloud.Monitoring",
    "resourceGroup": "rgname",
    "settings": {
      "workspaceId": "workspaceid"
    },
    "tags": null,
    "type": "Microsoft.Compute/virtualMachines/extensions",
    "typeHandlerVersion": "1.0",
    "virtualMachineExtensionType": "OmsAgentForLinux"
  }
]

后续步骤

若要查看代码、当前问题和版本,请参阅 GitHub 上的 custom-script-extension-linux 存储库