l0w.dev

Azure VM Run Command


Run Command について

Azure VM の Run Command は Azure Potal や PowerShell、 Azure CLI を使って、任意のスクリプトを VM で実行することができる機能です。

要するに Azure 管理インフラからアクセスできる VM のバックドアなのですが、 ネットワーク的に届かない場所にある VM のメンテナンスや、 オペレーションのミス・障害などで SSH や RDP で接続できなくなってしまった VM の回復などの目的で便利に使うことができます

Linux VM の Run Command (Azure CLI):

$ linux_vm=$(az vm show -g ... -n ... --query id -o tsv)
$ az vm run-command invoke --ids $linux_vm --command-id RunShellScript --scripts 'echo "hello, world."'
{
  "value": [
    {
      "code": "ProvisioningState/succeeded",
      "displayStatus": "Provisioning succeeded",
      "level": "Info",
      "message": "Enable succeeded: \n[stdout]\nhello, world.\n\n[stderr]\n",
      "time": null
    }
  ]
}

Windows VM の Run Command (Azure CLI):

$ windows_vm=$(az vm show -g ... -n ... --query id -o tsv)
$ az vm run-command invoke --ids $windows_vm --command-id RunPowerShellScript --scripts 'Write-Host "hello, world."'
{
  "value": [
    {
      "code": "ComponentStatus/StdOut/succeeded",
      "displayStatus": "Provisioning succeeded",
      "level": "Info",
      "message": "hello, world.",
      "time": null
    },
    {
      "code": "ComponentStatus/StdErr/succeeded",
      "displayStatus": "Provisioning succeeded",
      "level": "Info",
      "message": "",
      "time": null
    }
  ]
}

この機能は Azure REST API の Virtual Machines Run Commands Operation に投げられたスクリプト本文を含むリクエストが Azure VM エージェントの Run Command Extension に拾われて処理されます。 したがって Azure 管理インフラへのネットワーク接続が生きていければ使えません。

なお、ネットワークから切断されて Run Command も使えないほどダメになってしまった VM については、 まだ Serial Console を使うという手段が残っています。

スクリプト実行シェルのリダイレクトについて

上記の実行結果を見て気づくことは Run Command では stdout と stderr の出力結果が分離されて出てくるということです。 この仕様には各コマンドからの出力を正しく時系列で並べたログにできないという問題があります。 そういえば Ansible の shell module とかもそんな仕様で昔困ったような…。

Linux VM の Run Command (Azure Portal):

Windows VM の Run Command (Azure Portal):

そういうときは 2>&1 といったリダイレクトをつけてスクリプトを実行すればよいと思うのですが、 Run Command 機能ではそのようなカスタマイズができないので、 実行されるスクリプトが自分自身で適切なリダイレクトをしなくてはいけません。

Bash のスクリプトの場合は先頭に exec 2>&1 のように書くことで、自分を実行中のシェルプロセスのリダイレクトを簡単に設定できます。

exec 2>&1
echo one        # to stdout
two             # "command not found" to stderr
echo three      # to stdout
cat four.txt    # "No such file or directory" to stderr

PowerShell のスクリプトでこれと同等のリダイレクトはどうやるのかと探しまわったのですが、 見つけられませんでした。 しかたなく Stack Overflow で聞いてみたところ、 やはりそんな機能はないということでした。

そういうわけで不本意ですが、 PowerShell では次のようにスクリプトの本体をブロックや関数の中に入れて、 それにリダイレクトをつけて呼ぶ必要があるようです。

& {
    Write-Output one        # to stdout
    two                     # CommandNotFoundException to stderr
    Write-Output three      # to stdout
    Get-Content four.txt    # ItemNotFoundException to stderr
} 2>&1 | Out-String

また Run Command 機能においては、 このようにさらに最後に Out-String につなげて文字列化しないと エラーメッセージが stdout に出てきてくれませんでした。

Run Command 拡張の実装について

こういった挙動を解明するには Run Command 拡張の実装に深入りして調べる必要があると思い、 Windows の VM エージェントや Run Command 拡張のソースコードがないか探してみたのですが、 いまのところ見つけられていません。

Linux の Run Command 拡張については GitHub にソースコードがありました。 これは似たような機能を持つ Custom Script 拡張 v2 を元に作られており、コードもまだほとんど共通のようです。 Go 言語で書かれています。

Run Command のような Azure VM 拡張機能を自作できないかなあと思っていますが、 ドキュメントには自作に関する情報はなく、 それが可能かどうかもまだよくわかっていません。