AzureのVMで自身がVMで動いているかVMScaleSetで動いているか判断するにはIMDSが使える



### 背景

VMSSで動作させるイメージを作る際、イメージ作成時はイメージからVMを作成しそのVMで作業してイメージ化、イメージ化したあとはVMSSで実稼働させる形になると思います。
しかし、新しいイメージ化するにあたりVM上で作業しているときにサービスが立ち上がって動き始めると困るときがあります。そうなると、VMとVMSSを区別する手段が必要そうです。

※そもそも、こうやって作業を継ぎ足していくと、秘伝のタレと化したVMイメージが出来上がり、OSアップデートや後任が引き継いだ時にメンテナンスが困難になるので、再現性のある手順にするべきでしょう。packerや[AzureのImage Builder](https://docs.microsoft.com/en-us/azure/virtual-machines/image-builder-overview)を使い設定から生成できるようにすることで、自動起動設定だけしたイメージを都度作るといった手はあります。。。~~ですが、私の力不足でそこまでできませんでした~~


### 方法

VMとVMSSを区別するといった特殊なことはInstance MetaData Service(IMDS)を使うとできます。おそらくWindowsも同様にできることでしょう。

https://docs.microsoft.com/ja-jp/azure/virtual-machines/linux/instance-metadata-service?tabs=linux#instance-metadata

メタデータ内にVMScaleSetの名前の情報があるので、この有無で判別すればよいでしょう。

### Bashコード例

Bashではこんな感じに試すことができます。AzureのVM上で実行しないと意味がありません。

``` bash
vmScaleSetName=$(curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/vmScaleSetName?api-version=2021-05-01&format=text")

if [ "$vmScaleSetName" != "" ]; then
  echo "VMSS"
else
  echo "VM"
fi
```

結果はVMの場合は空文字列、VMScaleSetの場合はVMScaleSetの名前が設定されます。HTTPステータスコード自体は200になるので注意です。

また、`vmScaleSetName`は比較的新しいAPI-VERSIONでないと取得できません。

また、`format=text`の場合、`/metadata/instance`の後ろに`compute/vmScaleSetName`と辿るようにすると、その値だけが抜き出せるAPIになっています。便利。


### Systemdを使ったサンプル

例えば、VMSSではない場合はtd-agentを起動できないようにしてみましょう。
（td-agentには恨みはなくイメージ化で作るVMでは動いてほしくないという感じです）

td-agentの起動の管理はSystemdの仕組みを使っているので、Systemdでどうにかしてみます。

まずはVMSSであれば成功するサービスを書きます。サービス名は`isvmss.service`としましたが、もう少しいい名前をつけてあげてください。
また、スクリプトファイルを用意するのを横着して直接`bash -c`で実行していますがスクリプトに分けても構いません。

``` systemd:/usr/lib/systemd/system/isvmss.service
[Unit]
Description=Check if running on VMSS.
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/bin/bash -c '[ "$(curl -s -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/vmScaleSetName?api-version=2021-05-01&format=text")" != "" ]'
Type=oneshot
```

ネットワークが機能していないとcurlが使えません。`network-online.target`を依存関係に加えることでネットワークがオンラインになるまで待つようにします。

次にVMSSでないときは起動してほしくないサービスのUnitファイルをいじります。
今回はtd-agentのUnitファイルを書き換えます。以下、書き換え例です。

``` systemd:/usr/lib/systemd/system/td-agent.service
[Unit]
After=network-online.target isvmss.service
Requires=isvmss.service
```

Afterに`isvmss.service`を加えて、Requiresに`isvmss.service`を設定します。
Afterに`isvmss.service`を与えることで、`td-agent.service`を起動する前に`isvmss.service`を起動するように指示します。
Requiresに`isvmss.service`が起動できていることを前提にします。Requiresが書けると起動の成否を問わなくなるためAfterとRequiresの２つで設定しましょう。


その後、systemctlを操作して設定を反映します。

```
# Unitファイルの反映
sudo systemctl daemon-reload
```

ここまでで設定はOKです。

試してみましょう。

```
# おもむろに起動してみる
sudo systemctl start td-agent.service
# => A dependency job for td-agent.service failed. See 'journalctl -xe' for details.

# 失敗しているので、journalctl -xeで様子をみる
journalctl -xe
```

出てくるログを見てみるとisvmss.serviceが失敗したので、td-agentの起動を試さずにfailed扱いになっていることがわかります。

```
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: Check if running on VMSS....
-- Subject: Unit isvmss.service has begun start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit isvmss.service has begun starting up.
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: isvmss.service: main process exited, code=exited, status=1/FAILURE
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: Failed to start Am I working on VMSS?.
-- Subject: Unit isvmss.service has failed
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit isvmss.service has failed.
--
-- The result is failed.
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: Dependency failed for td-agent: Fluentd based data collector for Treasure Data.
-- Subject: Unit td-agent.service has failed
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit td-agent.service has failed.
--
-- The result is dependency.
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: Job td-agent.service/start failed with result 'dependency'.
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: Unit isvmss.service entered failed state.
Sep 16 15:17:30 fukasawahvmxxx systemd[1]: isvmss.service failed.
```

ただ、アップデートしたときに都度Unitファイルを書き直さないといけなくなり筋が悪いやりかたかもしれません。どうにかできないものか。


なお、IMDSエンドポイントはスロットリング（リクエスト制限）を行うため、他の何かがIMDSエンドポイントを使っている場合に制限にかかるかもしれません。なので、リトライすべきかもしれません。気になる場合は、次を参考にしてみましょう。

https://moznion.hatenadiary.com/entry/2017/09/28/223100