電通総研 テックブログ

電通総研が運営する技術ブログ

Azure Load Testing を REST API で操作

はじめに

ISID X(クロス)イノベーション本部 の池田です。
Azure Portal から Azure Load Testing を操作する方法については以下の記事もご覧ください。
tech.isid.co.jp

負荷試験の計画の中でいろいろな利用パターンでの試験により環境を分析するケースがあります。
しかし、Azure Portalからの操作では JMeterスクリプトのアップロードや参照できるリソース選択など選択項目が多く、複数パターンを何度も手動で作成するのは手間です。
そこでCLIから操作して複数テストを一括登録する方法を試しましたのでご紹介します。

利用ツール

通常のAzureリソースのCLI操作としては、Azure CLI や Azure Power Shell, ARMテンプレート を利用するのが一般的です。しかし、2023年2月にGAしたばかりの Azure Load Testing においては 2023年5月時点でこれらのツールでAzureリソース作成はできるものの詳細な設定ができませんでした。Azure Load Testing REST API では先行してテスト作成の機能が提供されていたため、こちらを利用して作成します。(現在はAzure CLIによるテスト作成もサポートされるようになっています)

今回のスクリプト実行環境は以下のとおりです。スクリプトの記載は環境依存のものが含まれますので、参考にされる際はご自身の環境に合わせて調整してください。

PowerShell 7.3.4
azure-cli  2.49.0
curl 8.0.1 (Windows) libcurl/8.0.1 Schannel WinIDN

試験環境の概要

今回使用するテスト環境の構成は以下のとおりです。App Service 既定のWebサイトをテスト対象としてAzure Load TestingからHTTPリクエストを連続送信します。その上でAzureリソースの負荷状況を Azure Load Testing のダッシュボードに表示する構成となります。

Azure Load Testingリソースの作成

Azureリソース作成はAzure CLIで機能提供されていましたので、この部分はこちらのコマンドを利用して作成します。

az load create --name ${loadTestName} --resource-group ${resourceGroupName} --location eastasia 

データプレーンURIの取得

Azure リソースマネージャーに対する要求では az rest コマンドを利用すると認証ヘッダーを自動付与できて便利です。
az rest でREST API を呼び出して作成されたリソースのデータプレーンエンドポイントを取得します。

$loadTest=(
    az rest `
    --method get `
    --uri https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.LoadTestService/loadTests/${loadTestName}?api-version=2022-12-01 `
    | ConvertFrom-Json)
$Endpoint=$loadTest.properties.dataPlaneURI
$Endpoint

以下のような出力が得られます。これでデータプレーン操作に必要なエンドポイントが取得できました。

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.eastasia.cnt-prod.loadtesting.azure.com

データプレーン用アクセストークンの取得

ここからは Azureリソースマネージャー へのアクセスではなくデータプレーンエンドポイントへの操作となります。データプレーン用のアクセストークンを取得する必要があります。

  1. 必要なアクセストーク
    Azure Load Testing REST API のリファレンスを参照すると、「https://cnt-prod.loadtesting.azure.com/.default%E3%80%8D%E3%82%92Scope%E3%81%A8%E3%81%97%E3%81%9F%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%8C%E5%BF%85%E8%A6%81%E3%81%A8%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82

    Load Test Administration - Create Or Update Test

  2. アクセストークンの取得
    az account get-access-token コマンドを利用して、該当スコープに対するアクセストークンを取得します。
    参考: az account get-access-token

$accessToken=(az account get-access-token `
--scope 'https://cnt-prod.loadtesting.azure.com/.default' `
--query 'accessToken' `
--output tsv)

テストの構成

データプレーンエンドポイントとアクセストークンがそろいましたので、続いてデータプレーンの操作をします。
データプレーンはTemplateファイルを用意してそれをcurlコマンドで投げ込む形で操作します。

  1. ID採番
    まずは、テストIDとして使用するGuidを採番します。
$testId=(New-Guid)
  1. テスト作成
    実際にAzure Load Testing REST APIを呼び出してテストを作成していきます。
    (参考:Load Test Administration - Create Or Update Test
curl `
--request PATCH `
--header "Authorization: Bearer $accessToken" `
--header "Content-Type: application/merge-patch+json" `
--url "https://${Endpoint}/tests/${testId}?api-version=2022-11-01" `
--upload-file TestTemplate.json

送信ファイルに記述するパラメータが以下となります。Azure Portal上で作成したテストの内容をAPIで取得して参考にしつつ作るのがおすすめです。
(参考:Load Test Administration - Get Test

{
    "displayName": "displayName_test1",
    "description": "",
    "keyvaultReferenceIdentityType": "SystemAssigned",
    "passFailCriteria": {
        "passFailMetrics": {}
    },
    "loadTestConfiguration": {
        "engineInstances": 10,
        "splitAllCSVs": false,
        "quickStartTest": false
    }
}
  1. アプリコンポーネント登録
    次にテスト結果画面に表示するアプリコンポーネント(Azure リソースのコレクション)を登録します。
    (参考: Load Test Administration - Create Or Update App Components
curl `
--request PATCH `
--header "Authorization: Bearer $accessToken" `
--header "Content-Type: application/merge-patch+json" `
--url "https://${Endpoint}/tests/${testId}/app-components?api-version=2022-11-01" `
--upload-file AppComponents.json

アプリコンポーネントは以下のように列挙していく形で登録します。参考とする既存設定の取得APIは以下です。
(参考:Load Test Administration - Get App Components

{
    "components": {
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda": {
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda",
            "resourceName": "app-demo-ikeda",
            "resourceType": "Microsoft.Web/sites",
            "resourceGroup": "rg-load-test",
            "subscriptionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "kind": "app"
        },
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo": {
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo",
            "resourceName": "plan-load-demo",
            "resourceType": "Microsoft.Web/serverfarms",
            "resourceGroup": "rg-load-test",
            "subscriptionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "kind": "app"
        }
    }
}
  1. メトリック登録
    続いて、テスト結果画面に表示するメトリックを登録します。
    (参考:Load Test Administration - Create Or Update Server Metrics Config
curl `
--request PATCH `
--header "Authorization: Bearer $accessToken" `
--header "Content-Type: application/merge-patch+json" `
--url "https://${Endpoint}/tests/${testId}/server-metrics-config?api-version=2022-11-01" `
--upload-file ServerMetricsConfig.json

前段で登録したアプリコンポーネントに対するメトリックを列挙します。参考とする既存設定の取得APIは下記です。
(参考:Load Test Administration - Get Server Metrics Config

{
    "metrics": {
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/Http5xx": {
            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/Http5xx",
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda",
            "metricNamespace": "microsoft.web/sites",
            "name": "Http5xx",
            "aggregation": "Total",
            "resourceType": "microsoft.web/sites"
        },
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/Requests": {
            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/Requests",
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda",
            "metricNamespace": "microsoft.web/sites",
            "name": "Requests",
            "aggregation": "Total",
            "resourceType": "microsoft.web/sites"
        },
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/HttpResponseTime": {
            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda/providers/microsoft.insights/metricdefinitions/HttpResponseTime",
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/sites/app-demo-ikeda",
            "metricNamespace": "microsoft.web/sites",
            "name": "HttpResponseTime",
            "aggregation": "Average",
            "resourceType": "microsoft.web/sites"
        },
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo/providers/microsoft.insights/metricdefinitions/CpuPercentage": {
            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo/providers/microsoft.insights/metricdefinitions/CpuPercentage",
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo",
            "metricNamespace": "microsoft.web/serverfarms",
            "name": "CpuPercentage",
            "aggregation": "Average",
            "resourceType": "microsoft.web/serverfarms"
        },
        "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo/providers/microsoft.insights/metricdefinitions/MemoryPercentage": {
            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo/providers/microsoft.insights/metricdefinitions/MemoryPercentage",
            "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg-load-test/providers/Microsoft.Web/serverfarms/plan-load-demo",
            "metricNamespace": "microsoft.web/serverfarms",
            "name": "MemoryPercentage",
            "aggregation": "Average",
            "resourceType": "microsoft.web/serverfarms"
        }
    }
}
  1. JMeterスクリプト登録
    以下の操作でテストで実行するJMeterスクリプトをアップロードします。
    (参考:Load Test Administration - Upload Test File
curl `
--request PUT `
--header "Authorization: Bearer $accessToken" `
--header "Content-Type: application/octet-stream content" `
--url "https://${Endpoint}/tests/${testId}/files/${NAME_JMX}?api-version=2022-11-01&fileType=JMX_FILE" `
--upload-file lt-demo.jmx

こちらが JMeter で作成したJMXファイルです。今回はHTTP Getリクエストをループするシンプルなスクリプトを使用しています。

<?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>
          <intProp name="LoopController.loops">-1</intProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">10</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></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">app-demo-ikeda.azurewebsites.net</stringProp>
          <stringProp name="HTTPSampler.port">443</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>

テストの実行

  1. テスト実行
    ボディ部にテストIDを指定することで登録したテスト内容を実行できます。テスト表示名はスクリプト内で日時から作成しています。
    (参考:Load Test Run - Create Or Update Test Run
curl `
--request PATCH `
--header "Authorization: Bearer $accessToken" `
--header "Content-Type: application/merge-patch+json" `
--url "https://${Endpoint}/test-runs/$(New-Guid)?api-version=2022-11-01" `
--data "{'testId': '${testId}','displayName':'TestRun_$((Get-Date).ToString("yyyy/MM/dd_HH:mm:ss"))'}"

実行結果の確認

  1. Azure Portalでの確認
    Azure Portal上で作成したテストが実行されていることを確認できます。
  2. アプリコンポーネント
    テスト画面の「アプリコンポーネント」からREST APIで登録したアプリコンポーネントが確認できます。
  3. メトリックの構成
    同じく「メトリックの構成」から表示メトリックも反映されていることが確認できます。

備考

ドキュメントが整備されていない場合、APIの利用方法を調べるのにブラウザのDevToolsでAzure Portalの通信内容を見るのも参考になります。今回もAzure Portal上での設定項目とAPI項目に差異があり、通信内容を見てみると新しいPreviewバージョンのAPIを利用していました。新しいサービスではドキュメント化が遅れているケースもありますので必要に応じて参考にしましょう。

まとめ

Azure Load Testing の操作を REST API を利用してスクリプト化することで、複数パターンの試験登録が簡単にできるようになりました。新しいサービスでは周辺ツールがまだ提供されていないケースもありますが、ベースとなる REST API が先行して提供されていることがあります。REST API による操作も活用することでAzureでできることが広がります。ご参考になれば幸いです。

私たちは同じチームで働いてくれる仲間を探しています。クラウドアーキテクトの業務に興味がある方のご応募をお待ちしています。

クラウドアーキテクト

執筆:@ikeda.jun、レビュー:@mizuno.kazuhiro
Shodoで執筆されました