Compartir a través de

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

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

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

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

自定义脚本扩展包括两个版本:

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

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

先决条件

支持的 Linux 分发

分发 X64 ARM64
Alma Linux 9.x+ 9.x+
Debian 10+ 11.x+
Azure Linux 2.x 2.x
OpenSUSE 12.3+ 不支持
Rocky Linux 9.x+ 9.x+
SLES 12.x+、15.x+ 15.x SP4+
Ubuntu 18.04+、20.04+、22.04+ 20.04+、22.04+

脚本位置

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

Internet 连接

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

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

提示

  • 此扩展的最高失败率是由于脚本中的语法错误。 验证脚本是否运行且没有错误。 将其他日志记录放入脚本,以便更轻松地查找失败。
  • 编写幂等脚本,确保意外多次运行这些脚本不会导致系统更改。
  • 确保在运行这些脚本时不需要用户输入。
  • 脚本允许运行 90 分钟。 任何更长时间都会导致该扩展预配失败。
  • 不要将重新启动置于脚本内。 重启会导致正在安装的其他扩展出现问题,并且重新启动后该扩展不会继续工作。
  • 如果脚本会导致在安装应用程序和运行脚本之前重新启动,可使用 Cron 作业或 DSC、Chef 或 Puppet 扩展等工具计划重启。
  • 请勿运行会导致 Azure Linu 代理停止或更新的脚本。 这可能会使该扩展处于“正在转换”状态,导致超时。
  • 该扩展只运行一次脚本。 如果想要在每次启动时运行脚本,则可以使用 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 字符串
typeHandlerVersion 2.1 int
fileUris https://github.com/MyProject/Archive/MyPythonScript.py array
commandToExecute python MyPythonScript.py \<my-param1> string
脚本 IyEvYmluL3NoCmVjaG8gIlVwZGF0aW5nIHBhY2thZ2VzIC4uLiIKYXB0IHVwZGF0ZQphcHQgdXBncmFkZSAteQo= string
skipDos2Unix false boolean
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
脚本 如果未设置 commandToExecute,则为必需项 /bin/sh 运行的 Base64 编码和(可选)gzip 脚本。
skipDos2Unix 可选 如果要跳过基于脚本的文件 URL 或脚本的 dos2unix 转换,请将此值设置为 false
timestamp 可选 仅更改此值以触发脚本的重新运行。 任何整数值都是可接受的,只要它与之前的值不同。
storageAccountName 可选 存储帐户的名称。 如果指定存储凭据,则所有 fileUris 值都必须是 Azure blob 的 URL。
storageAccountKey 可选 存储帐户的访问密钥。
managedIdentity 可选 用于下载文件的托管标识。 值包括 clientId(可选,字符串)和 objectId(可选,字符串),前者是托管标识的客户端 ID,后者是托管标识的对象 ID。

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

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

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

  • commandToExecute
  • script
  • fileUris

属性:skipDos2Unix

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

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

默认值为 false,这意味着执行 dos2unix 转换。 可以通过将 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 "Creating directories ..."
mkdir /data
chown user:user /data
mkdir /appdata
chown user:user /appdata

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

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.chinacloudapi.cn/privatecontainer/script1.sh"],
  "commandToExecute": "sh script1.sh",
  "managedIdentity" : {}
}

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

{
  "fileUris": ["https://mystorage.blob.core.chinacloudapi.cn/privatecontainer/script1.sh"],
  "commandToExecute": "sh script1.sh",
  "managedIdentity" : { "clientId": "31b403aa-c364-4240-a7ff-d85fb6cd7232" }
}
{
  "fileUris": ["https://mystorage.blob.core.chinacloudapi.cn/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 会引用配置文件:

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"}'

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

此示例使用了以下名为 script-config.json 的脚本文件:

{
  "fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"],
  "commandToExecute": "./config-music.sh"
}
  1. 通过使用所选文本编辑器或以下 CLI 命令创建脚本文件:

    cat <<EOF > script-config.json
    {
      "fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"],
      "commandToExecute": "./config-music.sh"
    }
    EOF
    
  2. 运行以下命令:

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

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

此示例使用以下 JSON 格式的内容:

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

运行以下命令:

az vm extension set \
  --resource-group tim0329vmRG \
  --vm-name tim0329vm --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings '{"commandToExecute": "apt-get -y update && apt-get install -y apache2"}'

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

使用公共配置文件来指定脚本文件的 URI:

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

使用受保护的配置文件来指定要运行的命令:

{
  "commandToExecute": "./config-music.sh"
}
  1. 使用所选文本编辑器或使用以下 CLI 命令创建公共配置文件:

    cat <<EOF > script-config.json
    {
      "fileUris": ["https://raw.githubusercontent.com/Microsoft/dotnet-core-sample-templates/master/dotnet-core-music-linux/scripts/config-music.sh"]
    }
    EOF
    
  2. 通过使用所选的文本编辑器或使用以下 CLI 命令创建受保护的配置文件:

    cat <<EOF > protected-config.json
    {
      "commandToExecute": "./config-music.sh"
    }
    EOF
    
  3. 运行以下命令:

    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 文件中。

sudo ls -l /var/lib/waagent/custom-script/download/0/

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

sudo cat /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 脚本扩展生成一个日志,位置如下:

sudo cat /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": "chinaeast",
    "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"
  }
]

Azure CLI 语法问题

Azure CLI 可以在多个 shell 环境中运行,但格式略有变化。 如果 Azure CLI 命令出现意外结果,请参阅如何成功使用 Azure CLI

后续步骤

要查看代码、当前问题和版本,请参阅 custom-script-extension-linux