Azure仮想マシンスケールセットを試す

船井総研デジタルのよもぎたです。
この記事は[株式会社船井総研デジタル Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fsdg)の4日目の記事です。

# サマリ
Azureの仮想マシンスケールセットを作成し、実際に仮想マシンが増減する様子を観察しようという主旨の記事です。

目論見としては、およそ次のような流れを考えています。
1. UbuntuにApacheをインストールしてCGIを有効にする
1. CPUに負荷がかかるCGIスクリプトを作成する
1. CGIスクリプトにアクセスして、実際にCPUに負荷がかかるのを確認する
1. 仮想マシンのイメージを取得する
1. 上記のイメージで仮想マシンスケールセットを作成し、ロードバランサー配下に配置する
1. ロードバランサーにアクセスして負荷をかけ続けて、スケールアウトするのが確認出来たら停止する
1. 仮想マシンが増減する様子を観察する

# 実際にやってみる

## 仮想マシンを準備する

仮想マシンの前に、必要に応じて仮想ネットワークとサブネット、それとネットワークセキュリティグループを用意します。ネットワークセキュリティグループの受信規則は、80/tcpをAnyから、22/tcpを自分のIPアドレスから許可するように設定しました。

仮想マシンはOSにUbuntu 22.04を、サイズをStandard B1lsで指定して、あとはデフォルトで作成しました。

## Apacheの準備をする

仮想マシンが起動したら、Apacheの準備をしていきます。

何はともあれ、OSのアップデートをします。
```bash
$ sudo apt -y update
$ sudo apt -y upgrade
```

続いて、Apache2をインストールします。
```bash
$ sudo apt -y install apache2
```

Apache2のCGIを有効にします。今回はDocumentRoot直下にCGIスクリプトを置く想定です。
```bash
$ sudo a2enmod cgid
Enabling module cgid.
To activate the new configuration, you need to run:
  systemctl restart apache2

$ sudo vi /etc/apache2/sites-available/000-default.conf
# ファイルの末尾に次の行を追加します
<Directory "/var/www/html">
        Options +ExecCGI
        AddHandler cgi-script .cgi
</Directory>
```

Apache2を再起動します。
```bash
$ sudo systemctl restart apache2
$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running)
```

テストスクリプトを作成します。
```bash
$ sudo vi /var/www/html/test.cgi
$ sudo chmod 755 /var/www/html/test.cgi
$ cat /var/www/html/test.cgi
#!/bin/bash

echo "Content-type: text/plain"
echo ""
echo "Hello world."
echo ""
```

テストスクリプトにアクセスしてみます。
```bash
$ curl -I http://www.example.com/test.cgi
HTTP/1.1 200 OK
---snip---
Content-Length: 14
Content-Type: text/plain
```

とりあえず、Apache2でCGIスクリプトを実行する準備が整いました。

## 負荷のかかるCGIスクリプトを用意する

先ほどのテストスクリプトを負荷テスト用にリネームします。
```bash
$ sudo mv -v /var/www/html/{test,loadtest}.cgi
renamed '/var/www/html/test.cgi' -> '/var/www/html/loadtest.cgi'
```

負荷テスト用スクリプトに修正します。
```
$ cat /var/www/html/loadtest.cgi
#!/bin/bash

echo "Content-type: text/plain"
echo ""
echo "Hello world."
echo ""

/usr/bin/dd if=/dev/random of=/dev/null bs=1M count=256 > /dev/null 2>&1
```

負荷テスト用スクリプトをテストします。
```bash
$ /var/www/html/loadtest.cgi
Content-type: text/plain

Hello world.
```

``Hello world.``が出力された後、プロンプトが返ってくるまで少し間があります。

負荷テスト用スクリプトにアクセスしてみます。
```bash
$ curl -I http://www.example.com/loadtest.cgi
HTTP/1.1 200 OK
---snip---
Content-Length: 14
Content-Type: text/plain
```

負荷テスト用スクリプトに無事アクセスできるのが確認できたので、``ab``コマンドで大量アクセスしてみます。

負荷をかける側
```bash
$ ab -c 16 -t 5 -n 6400 http://www.example.com/loadtest.cgi
```

負荷をかけられた側
```bash
$ top -n 1 | head -10
top - 01:25:30 up 4 min,  1 user,  load average: 2.53, 0.63, 0.23
Tasks: 137 total,  17 running, 120 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,100.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    402.8 total,      4.2 free,    209.1 used,    189.4 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    169.2 avail Mem
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   1098 www-data  20   0    3860   2644   1556 R   6.7   0.6   0:00.70 dd
   1099 www-data  20   0    3860   2648   1556 R   6.7   0.6   0:00.70 dd
   1112 www-data  20   0    3860   2580   1492 R   6.7   0.6   0:00.69 dd
```
無事負荷がかかることが確認できました。
いったん仮想マシンをシャットダウンして、イメージを取得することにします。

## 仮想マシンのイメージを取得する

仮想マシンのイメージを取得する手順については、[こちらのページ](https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/capture-image-resource)に詳しい記載があります。
簡単に言うと、仮想マシンの概要から「キャプチャ」をクリックして、あとはウィザードに従うだけです。

私は``loadtest``という名前でイメージを取得しました。イメージの取得には結構時間がかかりました。

なお、イメージを取得したVMは起動できなくなるので、ご注意ください。

## 仮想マシンスケールセットを作成する

取得した仮想マシンイメージから、仮想マシンスケールセットを作成します。
手順については、[こちらのページ](https://learn.microsoft.com/ja-jp/azure/virtual-machines/windows/create-vm-generalized-managed)に詳しい記載があります。

その前に、フロントに置くロードバランサーを作成します。手順は[こちらのページ](https://learn.microsoft.com/ja-jp/azure/load-balancer/quickstart-load-balancer-standard-public-portal)に詳しい記載があります。ただ、パラメータは自分の環境に合わせて適宜変更しました。

仮想マシンのイメージの概要から、「VMSSの作成」をクリックします。そうすると「仮想マシン スケール セットの作成」のウィザードに遷移しますので、お好みのパラメータを入力して仮想マシンのスケールセットを作成します。

ネットワークの設定では「負荷分散のオプション」で「Azure Load Balancer」を選択し、先ほど作成したロードバランサーを選択します。この時点でロードバランサーを新規作成することもできるようです。

次にスケーリングの設定です。私はパラメータを次のように設定しました。
* 「初期インスタンス数」を``1``に設定
* 「スケーリングポリシー」を``Autoscaling``に設定
* 「インスタンスの最大数」を``4``に設定
* 「期間 (分)」を``5``に設定

``http://<ロードバランサーのフロントエンドIPアドレス>/``にアクセスして、応答があることを確認します。

Azure CLIから、仮想マシンスケールセットの状態を確認しておきます。
```bash
$ az vmss list-instances --resource-group <ResourceGroupName> --name loadtest --output table
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded          
```
仮想マシンが一つだけあることが分かります。

## 仮想マシンスケールセットに負荷をかける

ようやくここまで来ました。ここからが本番です。
仮想マシンスケールセットに負荷をかけつつ、Azure CLIで仮想マシンスケールセットの状態（VM数）を確認していきます。コマンドはそれぞれ次の通りです。

負荷をかける側
```bash
$ ab -c 16 -t 5 -n 64000 http://www.example.com//loadtest.cgi
```

仮想マシンスケールセットの状態を確認するAzure CLIコマンド
```bash
$ for i in {1..150}; do az vmss list-instances --resource-group <ResourceGroupName> --name loadtest --output table; echo "sleep 10s"; sleep 10; done
```

ではいざ、上記2つのコマンドを実行します。

仮想マシンスケールセットのVM数の推移は下記のとおりです。
```bash
$ for i in {1..150}; do az vmss list-instances --resource-group <ResourceGroupName> --name loadtest --output table; echo "sleep 10s"; sleep 10; done
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded            
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Creating
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Creating
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Succeeded
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Succeeded
3             True                  japaneast   VirtualMachineScaleSet    loadtest_3  Creating 
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Succeeded
3             True                  japaneast   VirtualMachineScaleSet    loadtest_3  Succeeded
---snip---
# 負荷をかけるのを停止
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Succeeded
3             True                  japaneast   VirtualMachineScaleSet    loadtest_3  Deleting
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded
1             True                  japaneast   VirtualMachineScaleSet    loadtest_1  Succeeded
2             True                  japaneast   VirtualMachineScaleSet    loadtest_2  Succeeded
---snip---
InstanceId    LatestModelApplied    Location    ModelDefinitionApplied    Name        ProvisioningState    ResourceGroup    VmId
------------  --------------------  ----------  ------------------------  ----------  -------------------  ---------------  ------------------------------------
0             True                  japaneast   VirtualMachineScaleSet    loadtest_0  Succeeded   
```

負荷をかけ始めてしばらくして追加の仮想マシンがデプロイされること、負荷をかけるのを止めてから、順次仮想マシンが削除されることが確認できました。

負荷をかける前後を含めた仮想マシンのCPU負荷のグラフは次の画像のとおりです。

![vmss-cpu-graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2822136/6299f856-55c1-b993-2dad-0f7b6cb5a78c.png)

負荷をかけ始めるとともに急上昇し、負荷を止めると急下降しているのがご確認いただけると思います。

# まとめ

無事、仮想マシンスケールセットで負荷に応じて仮想マシンが増減する様子を確認できました。当初の目標はひとまず達成です。しかし、実際にやってみて課題も感じました。

それは、負荷が仮想マシン追加の閾値を超えてから、実際に追加の仮想マシンが稼働し始めるまでの時間が長いということです。あらかじめ負荷がかかることが分かっていて、事前に手動でスケールアウトさせておくという使い方なら問題ないでしょう。しかし、突発的な負荷に迅速に対応するには不十分だと感じました。仮想マシンのプロビジョニングにはどうしても時間がかかるので仕方ないのでしょう。

ここはやはり、コンテナーの出番でしょうか。というわけで、近いうちにAKSのスケーリングも試してみたいと思います。

最後までお読みいただきありがとうございました。