スケーラブルな負荷テスト環境をPipelineで必要なときに必要な量だけ作る! - JMeter / ACI / Terraform / Azure Pipeline - #Azure リレー
2020-04-01
azblob://2022/11/11/eyecatch/2020-04-01-jmeter-aci-terraform-000.jpg

先週の三上さんの アプリケーション設定と接続文字列がなぜあるのか #Azure リレー に引き続き、 毎週水曜日にAzure関連の記事を挙げる Azure リレー第5回を担当します。

今回はパイプラインを利用した負荷試験の環境構築、実行の自動化の紹介です。 元ネタはマイクロソフト社が提供しているAzure -Samplesの中にあります。

Scalable cloud load/stress testing pipeline solution with Apache JMeter and Terraform to dynamically provision and destroy the required infrastructure on Azure.

https://github.com/Azure-Samples/jmeter-aci-terraform

環境構築からアプリケーションの実行、さらに不要になったリソースの削除までをパイプラインで一気通貫で実装されてます。

技術要素
・コンテナ、コンテナリポジトリ、ソースコードリポジトリの利用
・Terraformによる環境構築
・実行ログはすべてPipelineの実行履歴に記録される
・Azure Pipelineを利用した自動化

このような技術要素の概要を掴むのに良いサンプルなので、ぜひ見てみてください。何がどのように動き、どこに何を設定すれいいのかの観点が理解できます。

では、早速みていきましょう。

どのように動作するのか?

実行時のステップは2つに別れています。
「①コンテナイメージを自動的に生成する」と「②コンテナを利用して負荷環境を構築し負荷試験を行う」になります。

概要図

まずは、①です。Pipelineのjmeter-docker-buildでJMeterのdockerコンテナにプラグインを追加してビルドしACRへコンテナイメージを登録します。これにより、JMeterのコンテナイメージが自分の手元のACRに登録されるのでこれから先はこのイメージを利用して環境を構成します。

次は、②です。Pipelineのjmeter-load-testで、JMeterの設定ファイルであるJMXファイルを共有フォルダに配置しterraformを利用してJMeterコンテナ(マスター、スレーブ)をAzure Container Instanceで起動します。その後にJMeterの実行し、実行終了後に評価結果をパイプラインの実行履歴に紐付けて記録します。最後にAzure Container Instance などAzureリソースを一括で削除します。

このような環境がパイプライン実装されることで、簡単な手順で誰でも必要なときだけリソースを利用できるようになります。さらにサンプルでは、トラフィック量をふやしたいときもJMeterのスレーブの数を実行時のパラメータレベルで増やすことが可能になっているので、大量のトラフィックをさばくことも可能です。

設定・実行方法

紹介した元ページにパイプラインを作成する手順があります。
https://github.com/Azure-Samples/jmeter-aci-terraform#getting-started

ここでは手順の補足と具体的に動作させたときにどこに反映されるかを合わせて紹介します。以下からが実際の手順になります。
ぜひ、動かしてみてください。動かさなくても、結果も貼ってあるのでイメージはつくと思いますのでお付き合いください!

事前に必要なもの

Azure CLI
マイクロソフトのAzure CLIのインストール手順

Azure DevOps CLI
手順の中でインストールされるので事前作業は不要

Shell
何でもいいと思います。
参考)Windowsで自分で利用している環境だとこんな感じです。
wsl2のインストール手順
コンソールはFluentTerminalインストール手順

jq
Linuxでjsonにクエリーをかけられるコマンドです。パイプラインで強力です。
https://stedolan.github.io/jq/

Service Principal
マイクロソフトが紹介しているCLIで作成手順
Terraformが紹介しているCLIで作成手順
設定の具体例)
ログインして今回作成する対象のサブスクリプションを指定する。
<subscription name or subscription id>を変更してください。

az login
az account set -s <subscription name or subscription id>
az account show

表示されたサブスクリプション名が正しいことを確認してください。

{
  "environmentName": "AzureCloud",
  "homeTenantId": "00000000-0000-0000-0000-000000000000",
  "id": "00000000-0000-0000-0000-000000000000",
  "isDefault": true,
  "managedByTenants": [
    {
      "tenantId": "00000000-0000-0000-0000-000000000001"
    }
  ],
  "name": "subscriptionname",
  "state": "Enabled",
  "tenantId": "00000000-0000-0000-0000-000000000002",
  "user": {
    "name": "xxxxxxxxxxx@aaaa.com",
    "type": "user"
  }
}

スコープ を今回利用するサブスクリプションに、権限をContributorに設定しサービスプリンシパルを作成します。nameパラメータでサービスプリンシパルに名前をつけられます。利用目的が分かるように設定することをオススメします。scopesをリソースグループなどで絞ることで、さらに権限を絞り安全に作業できますが、ここではそこまで絞りません。
ここでは、<subscription id>を変更してください。

az ad sp create-for-rbac --name="http://jmeterdemo-20200331" --role="Contributor" --scopes="/subscriptions/<subscription id>"

appidとpasswordはのちほど利用するのでメモしておいてください。

Creating a role assignment under the scope of "/subscriptions/0a60c63a-1bb8-4157-8702-eff1eafc6a87"
{
  "appId": "00000000-0000-0000-0000-00000000000i",
  "displayName": "jmeterdemo-20200331",
  "name": "http://jmeterdemo-20200331",
  "password": "00000000-0000-0000-0000-00000000000p",
  "tenant": "00000000-0000-0000-0000-00000000000t"
}

Azure Container Registry
マイクロソフトが紹介しているCLIで作成手順
設定例)
ACRを作成するリソースグループを作成する

az group create --name rg-jmeterdemo --location japaneast

ACRを作成する。ACRの名前は変更してください。
ACRの名前は、'^[a-zA-Z0-9]*$'の命名規則があります。ただし、DevOpsとつなぐときに小文字にしておかないとエラーになるようです。 ご注意ください。

az acr create --resource-group rg-jmeterdemo --name acrjmeterdemo --sku Basic

ACRのアクセスキーを取得するために、管理者モードをオンにします 。

az acr update -n acrjmeterdemo --admin-enable true

アクセスキーを取得する。
パスワードのどちらか一方をメモしておいてください。

az acr credential show --name acrjmeterdemo
{
  "passwords": [
    {
      "name": "password",
      "value": "000000000000000000000000000000p1"
    },
    {
      "name": "password2",
      "value": "000000000000000000000000000000p2"
    }
  ],
  "username": "acrjmeterdemo"
}

Azure DevOpsの組織とプロジェクトの作成
マイクロソフトが紹介している作成手順
作成例)
Azure DevOpsのサイトにアクセスしてください。
アクセスするとテナント名でDevOpsの組織が作成されます。

https://go.microsoft.com/fwlink/?LinkId=307137

ログイン後にリージョン選択

組織の作成完了(masaosakurai)とプロジェクト(jmeterdemo)の作成する

Getting Started
1.サンプルのリポジトリをRepoにインポート

azにazure-devopsの拡張モジュールをインストールします。

az extension add --name azure-devops

azでAzure DevOpsの環境設定を行います 。
DevOpsの組織とプロジェクトを作成したものに変更します。

az devops configure --defaults organization=https://dev.azure.com/<organization> project=<projectname>

コンテナリポジトリを作成し、githubのAzure SampleのリポジトリをRepoで作成したリポジトリにインポートします。
今後このリポジトリがPipelineからリソースを取得する元になります。

変数の設定

REPOSITORY_NAME=jmeter-load-test
REPOSITORY_URL=https://github.com/Azure-Samples/jmeter-aci-terraform

リポジトリの作成

az repos create --name $REPOSITORY_NAME

リポジトリをコピー

az repos import create --git-source-url $REPOSITORY_URL --repository $REPOSITORY_NAME

以下のURLにブラウザでアクセスしてください。
<organization>は自分の作成した組織名に変更してください。
https://dev.azure.com/<organization>/jmeterdemo/_git/jmeter-load-test
リポジトリでコピーしたコードの内容が確認できます。

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

事前準備に書きましたので、そちらをご確認ください。

3.テナントID、サブスクリプションIDを取得する

az account show

テナントIDとサブスクリプションIDをメモしておいてください。

{
  "environmentName": "AzureCloud",
  "homeTenantId": "00000000-0000-0000-0000-000000000000",
  "id": "00000000-0000-0000-0000-000000000000",
  "isDefault": true,
  "managedByTenants": [
    {
      "tenantId": "00000000-0000-0000-0000-000000000001"
    }
  ],
  "name": "subscriptionname",
  "state": "Enabled",
  "tenantId": "00000000-0000-0000-0000-000000000002",
  "user": {
    "name": "xxxxxxxxxxx@gmail.com",
    "type": "user"
  }
}

4.Pipelineに変数とシークレットを設定する

コンソールからbash変数に、サービスプリンシパル、ACRのクルデンシャル情報を設定する。 <>の中身を変更してください。

CLIENT_ID=<service principal appId>
CLIENT_SECRET=<service principal password>
TENANT_ID=<tenantId>
SUBSCRIPTION_ID=<subscriptionid>
ACR_NAME=<acr name>
ACR_PASSWORD=<acr password>

Pipelineの変数を設定する。
まずは、サービスプリンシパルの情報をAzure Pipelineの変数グループとして登録する。 パスワードを後からシークレットとして登録するため変数グループのIDを取得しておく。

PRIN_GROUP_ID=$(az pipelines variable-group create \
  --name JMETER_AZURE_PRINCIPAL --authorize \
  --variables ARM_CLIENT_ID=$CLIENT_ID \
              ARM_TENANT_ID=$TENANT_ID \
              ARM_SUBSCRIPTION_ID=$SUBSCRIPTION_ID \
  | jq .id)

サービスプリンシパルのパスワードをシークレットとして変数グループに追加。

az pipelines variable-group variable create \
 --group-id $PRIN_GROUP_ID --secret true \
 --name ARM_CLIENT_SECRET \
 --value $CLIENT_SECRET

Azure PipelineのLibraryのJMETER_AZURE_PRINCIPALを開くと変数として登録したものと、シークレットとして登録したものがそれぞれちゃんと設定されれていることがわかる。

次は、同じようにTerraformで利用するパラメータを設定する。こちらも通常の変数とパスワードを分けて登録する。

SETT_GROUP_ID=$(az pipelines variable-group create \   --name JMETER_TERRAFORM_SETTINGS --authorize \
   --variables TF_VAR_JMETER_IMAGE_REGISTRY_NAME=$ACR_NAME \
               TF_VAR_JMETER_IMAGE_REGISTRY_USERNAME=$ACR_NAME \
               TF_VAR_JMETER_IMAGE_REGISTRY_SERVER=$ACR_NAME.azurecr.io \
               TF_VAR_JMETER_DOCKER_IMAGE=$ACR_NAME.azurecr.io/jmeter \
   | jq .id)

ACRのパスワード。グループは同じ。

az pipelines variable-group variable create \
 --group-id $SETT_GROUP_ID --secret true \
 --name TF_VAR_JMETER_IMAGE_REGISTRY_PASSWORD \
 --value $ACR_PASSWORD

改めてDevOpsでPipelineのLibraryを確認してみましょう。

5.JMeterのDockerをBuildするパイプラインを作成する。

パイプラインを作成し、初回のパイプラインを実行する 。

PIPELINE_NAME_DOCKER=jmeter-docker-build

az pipelines create --name $PIPELINE_NAME_DOCKER \ 
    --repository $REPOSITORY_NAME \
    --repository-type tfsgit --branch master \
    --yml-path pipelines/azure-pipelines.docker.yml

実行結果をブラウザで確認してみましょう。

パイプライン名をクリックして、実行履歴をさらにクリックすると各ステップでの実行内容、処理時間が分かります。成功していれば緑の丸にチェックが入ります。もしエラーした場合はどこかで設定間違っているかもしれませんのでログを確認してください。

6.JMeterを実行するパイプラインを作成する

ロードテストを実行するパイプラインを作成する。

PIPELINE_NAME_JMETER=jmeter-load-test

az pipelines create --name $PIPELINE_NAME_JMETER \
 --repository $REPOSITORY_NAME \
 --repository-type tfsgit --branch master --skip-first-run \
 --yml-path pipelines/azure-pipelines.load-test.yml

JMXファイル名、 JMeterスレーブ数を実行時の変数として追加する。 

az pipelines variable create --pipeline-name $PIPELINE_NAME_JMETER --name TF_VAR_JMETER_JMX_FILE --allow-override
az pipelines variable create --pipeline-name $PIPELINE_NAME_JMETER --name TF_VAR_JMETER_SLAVES_COUNT --allow-override

パイプラインのALLを選択し、作成したパイプラインを確認する。

パイプライン名、右上のEditを選択するとリポジトリのパイプラインのコードが確認できる。さらに、Variablesを選択すると追加で設定した実行時に必要になるパラメータが表示される。

ここまでできていれば、ほぼ完了です。

7.JMeterテスト用のJMXファイルの変更(オプション)

デフォルトでは、リポジトリのjmeterフォルダにあるsample.jmxを利用します。azure.microsoft.comの443ポートに対してリクエストを発行します。必要に応じて変更してください。

8.JMeterの負荷をかけるのパイプラインを実行する

JMETER_JMX_FILE=sample.jmx
JMETER_SLAVES_COUNT=1

az pipelines run --name $PIPELINE_NAME_JMETER \
    --variables TF_VAR_JMETER_JMX_FILE=$JMETER_JMX_FILE TF_VAR_JMETER_SLAVES_COUNT=$JMETER_SLAVES_COUNT

パイプラインのjmeter-load-testの実行ログを参照すると実行中だと実行中のログが参照されます。こちらを見ていると、リアルタイムにコンソールログは表示され実行状態を知ることができます。

実行後の結果を見てみよう

1.実際に作成された環境

jmeterというリソースグループにコンテナインスタンスが自動的に作成されて処理が実行されている。処理が完了すると以下のリソースは自動的にすべて削除されます。

2.パイプラインの実行ログ

実行ログを見ると、15分で全行程が正常に完了していました。

実行履歴(runs)から、事項ログを参照し黄色くしてあるRelatedの1publishedをクリックすると、Jmeterそのもののログが取得できる。

一括ダウンロードすると、JMeterのダッシュボードでより詳細のレポートが確認できる

3.テストプランの負荷試験の結果表示

テストプランのrunsを見ると実際の実行した結果が表示されます。

詳細を見ると、実際の各リクエストの処理時間が参照できます。

コードを見てみる

実際に動くものが目の前にできるとソースコードが読みやすくなるので、コードも簡単に紹介します。

1.JMeterのビルドを実行しているDockerFile

ソースコードのリポジトリのdocker/Dockerfileが利用されています。jmeterの5.1.1のコンテナイメージが利用されており、buildの中でjmeterのプラグインのインストールも合わせてインストールしています。プラグインが追加で必要な場合は、ここに同じように追加すればよいです。
https://github.com/Azure-Samples/jmeter-aci-terraform/blob/master/docker/Dockerfile

2.環境を構築しているTerraform

terraformのフォルダに、リソースを構成するmain.tf、パラメータの初期値や設定するvariables.tfなどが配置してあります。ACIで利用されているコンテナのサイズも、variable.tfで定義されているので変更したい場合はここを変更してください。
https://github.com/Azure-Samples/jmeter-aci-terraform/tree/master/terraform

3.pipelineを作成しているyml

はじめにdockerでjmeterのコンテナイメージを生成しているパイプラインはpipelinesフォルダのazure-pipelines.docker.ymlです。
https://github.com/Azure-Samples/jmeter-aci-terraform/blob/master/pipelines/azure-pipelines.docker.yml
簡単に中身を見ていくと、ci triggerが作成したリポジトリのmasterブランチのdockerフォルダに設定されているのがわかります。ymlの中で、作成した変数グループを指定し、実際のscriptコマンドで実行しています。

次に環境を構築し、負荷試験を実行しているのは、azure-pipelines.load-test.ymlです。
https://github.com/Azure-Samples/jmeter-aci-terraform/blob/master/pipelines/azure-pipelines.load-test.yml
この中でterraformを実行しterraformの中でJMeterを起動しています。その後、Waitをかけて実行が終わるのを待ち必要な実行ログなどを回収し、最後にterraform destroyを実行して作成したリソースをすべて削除しています。
ここを見ていると、インフラをパイプラインで作成するときの変数やシークレットの取り回しなどがどうなっているのかがわかります。

リソースのクリーンアップ

最後に作成したリソースをちゃんと削除しておきましょう。
Azure DevOpsのプロジェクト、ACRを配置するために作成したリソースグループ、サービスプリンシパルを削除すれば完了です。

1.Azure DevOpsプロジェクトの削除

プロジェクトの概要ページの設定ボタンをクリックして、Delete projectを実行設定ボタンは以下の黄色のボタン。マウスオーバーしないと出てこないです。

2.ACRを作成したリソースグループの削除

リソースグループの名前を確認して実行してください。

az group delete --name rg-jmeterdemo -y

3.サービスプリンシパルを削除する

idは、サービスプリンシパル名かオブジェクトidでいいみたいです。

az ad sp delete --id http://jmeterdemo-20200331

まとめ

さて、今回のご紹介はここまでです。いかがでしたでしょうか?
実際に動作させたときのキャプチャを中心にしたので作業してなくてもイメージは掴んで頂けたのではないかと思います。

現在、新しいことがいろいろ出てくる中で、githubでSampleとして公開されているソースが非常に理解に役立ちます。ただし、実行するときにセキュリティに注意してください。 Azure Samplesはこの他にも、1.4kのリポジトリがあるようなので面白いものがあればまた紹介したいと思います。

ぜひ、みなさんも興味のわくサンプルを探してみて動かしてみてください。
https://github.com/Azure-Samples