Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

Azure Load TestingをGitHubActionsから実行してみた!

こんにちは!オルターブースのいけだです!

Azure Load Testingを使って簡単な負荷テストをGitHubActionsで実行するといった内容を検証してみました。 こちらの公式ドキュメントに沿って進めていきます。

learn.microsoft.com

テスト用のサンプルサイト

こんなサンプルサイトを用意しておきます。 サイト更新の度に合計数が増える。といった簡単なものをAzure App ServicesにGitHubActionsでCI/CDできるようにしておきます👇

aadojo.alterbooth.com

サービスプリンシパルの作成

それでは手順に戻ります。まずはGitHub ActionsのワークフローからAzureにアクセスできるように共同作成者ロールを割り当てる作業です。 AzureにログインしてCloud Shell を立ち上げます。

以下のコマンドを入力します。

subscription=$(az account show --query "id" -o tsv)
echo $subscription
az ad sp create-for-rbac --name "my-load-test-cicd" --role contributor \
                         --scopes /subscriptions/$subscription \
                         --sdk-auth

するとサービスプリンシパルを表すJSONが出力されます、 これをメモしておきます。

{
  "clientId": "00000000-0000-0000-0000-000000000000",
  "clientSecret": "00000000-0000-0000-0000-000000000000",
  "subscriptionId": "00000000-0000-0000-0000-000000000000",
  "tenantId": "00000000-0000-0000-0000-000000000000",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
  "resourceManagerEndpointUrl": "https://management.azure.com/",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com/",
  "managementEndpointUrl": "https://management.core.windows.net/"    
}

続いて、サービスプリンシパルに Load Test Contributor ロールを割り当てます。

object_id=$(az ad sp list --filter "displayname eq 'my-load-test-cicd'" --query "[0].id" -o tsv)

az role assignment create --assignee $object_id --role "Load Test Contributor" --scope /subscriptions/$subscription --subscription $subscription

Azure Load Testing

AzureポータルからLoad Testingをデプロイします。

learn.microsoft.com

テストスクリプトはJMeterで作成します。

JMeterのインストールと使用方法の参考記事

保存すると以下のようなファイルが作成されます。

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <stringProp name="LoopController.loops">1</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">500</stringProp>
        <stringProp name="ThreadGroup.ramp_time">0</stringProp>
        <boolProp name="ThreadGroup.scheduler">true</boolProp>
        <stringProp name="ThreadGroup.duration">100</stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="HTTPSampler.domain">******.azurewebsites.net</stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">/</stringProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>
  • ThreadGroup.main_controller:
    • ループコントローラーがどのように動作するかを制御します。ここでは、"continue_forever"が"false"、"loops"が"1"なので、テストは一回だけ実行されます。
  • ThreadGroup.num_threads:
    • このパラメータはスレッドの数(つまり仮想ユーザーの数)を指定します。ここでは500と設定したので、500の仮想ユーザーがテストを行います。
  • ThreadGroup.ramp_time:
    • これは全てのユーザーが起動するまでの時間(秒)を指定します。ここでは0と設定したので、すべてのユーザーがすぐに起動します。
  • ThreadGroup.scheduler:
    • スケジューラを使用するかどうかを制御します。ここでは"true"と設定したので、スケジューラが使用されます。
  • ThreadGroup.duration:
    • スケジューラが有効な場合、このパラメータはテストの実行時間(秒)を指定します。ここでは100と設定したので、テストは100秒間実行されます。
  • HTTPSampler.domain:
    • これはリクエストが送信されるドメイン(またはIPアドレス)を指定します。
  • HTTPSampler.port:
    • これは接続するポート番号を指定します。ここでは空欄です。
  • HTTPSampler.protocol:
    • これは使用するプロトコルを指定します。ここでは "https" を指定しています。
  • HTTPSampler.path:
    • これはリクエストのパスを指定します。
  • HTTPSampler.method:
    • これは使用するHTTPメソッド(GET、POST、PUTなど)を指定します。ここでは "GET" を指定してます。

ポータルからこのjmxファイルをアップロードしてテストを実行してみます。

確認して作成するとテストが実行され、実行したjmxファイルと設定ファイルのセットをダウンロードできます。

因みに設定ファイルの内容はこちらになります。

displayName: Get_*********.azurewebsites.net
testPlan: quick_test.jmx  #jmxファイル名
description: 
engineInstances: 3 #エンジンインタンスの数
testId: *****************************

Github

GitHubレポジトリのシークレットに先程のJSONで出力されたサービスプリンシパルの情報を設定しておきます。 名前はAZURE_CREDENTIALSとして、値には先程のJSONを貼り付けます。

デプロイしてテスト実行

先程ポータルからダウンロードした.jmx.yamlファイルをワークディレクトリの直下に保存します。

├── .git
├── .github
│ └── workflows
│    └── workflow.yml
├── src
│ └── ...
└── quick_test.jmx
└── quick_test_config.yaml

.github/workflow/内のworkflow.ymlファイルに以下のフローを追記します。

...

  loadTest:
    name: Load Test
    needs: deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout GitHub Actions 
        uses: actions/checkout@v2
          
      - name: Login to Azure
        uses: azure/login@v1
        continue-on-error: false
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}
          
      - name: 'Azure Load Testing'
        uses: azure/load-testing@v1
        with:
          loadTestConfigFile: 'quick_test_config.yaml' #configFileName
          loadTestResource: 'my-load-testing' #ResourceName
          resourceGroup: 'my-load-test-rg' #ResourceGroupName
              
      - uses: actions/upload-artifact@v2
        with:
          name: loadTestResults
          path: ${{ github.workspace }}/loadTest

github.com

変更が終わったら、GitHubへPushします。 デプロイ後にテストが実行されたようです。 ここからテスト結果をcsv形式でダウンロードもできます。

設定ファイルに条件を追加する事でテストに不合格の条件を追加できます。

displayName: Get_*********.azurewebsites.net
testPlan: quick_test.jmx
description: 
engineInstances: 3
testId: *****************************

failureCriteria: 
  - percentage(error) > 50

エラー割合が50%以上なので不合格になりました…💦

価格について

料金ですが、2023.06.13時点では下記の価格になります。

内容 条件/価格
基本 1か月あたり50仮想ユーザー時間が含まれていて1,404円
1仮想ユーザー時間の追加使用料 9,950時間までは追加時間1時間あたり21.07円
9,950時間以上の使用量 追加時間1時間あたり10.53円

https://azure.microsoft.com/ja-jp/pricing/details/azure-devops/load-testing/

Azure Load Testingnリソースに対して基本料金 1,404円かかります。 基本料金の中に50仮想ユーザー時間/月が含まれています。

この仮想ユーザー時間というのは、以下の式で算出できるとQ&Aに記載されてます

仮想ユーザー時間 = (算定仮想ユーザー数 * 算定稼働時間 (分単位)) / 1 時間あたり 60 分。

アクティブな仮想ユーザーの数は 10 秒ごとにカウントされ、等価な分数の仮想ユーザー時間を使用して課金されます。

たとえば、1,000 人の仮想ユーザーが 30 分間使用すると、仮想ユーザー時間 500 が課金されます。

なお今回実行した例ですと、

1500 (500ユーザー × エンジンインスタンス 3 ) × 1.67 (100秒) / 60 = 41.75

なので今回は、基本料金枠の50仮想ユーザー時間/月 内で収まる計算になります。

料金の概算は料金計算ツールを使用するのが分かりやすいと思います。

さいごに

今回、CI/CDに組み込むという部分だけ抜粋しましたが、 公式のチュートリアルではここからボトルネックを特定して改善するといった一連の流れも記載されています。

負荷テスト自体が初めてでしたが、こういった形で必要な時に必要な分だけ負荷テストを実行できるのはスピード感がありますし、 ボトルネックの早期発見に繋がりそうですね!

今後もJMeterの使い方含めて継続して学習していこうと思います!

参考にさせて頂いた記事

learn.microsoft.com

learn.microsoft.com