k6 応用例その1:ログイン認証型Webサイトへの負荷試験の一括実行

クラウドソリューション開発部の藤井です。

負荷試験ツール k6 の紹介記事の第2弾として、応用例を解説させていただきます。
k6 って何だろうという方は、ぜひこちらの記事をご参照ください。


k6 は公式サイトに「シンプルなテストでも、無いよりはまし」とあるように、最初から複雑な設定やコードがフルセットで必要ではなく、JavaScriptで提供されている小さな独立したモジュールを柔軟に組み合わせて使うことができるようになっています。

今回は試験対象として、Cookieを使ったログイン機能を持つ典型的なWebアプリケーションを想定し、少し複雑な以下の方法について、コードの例を交えて解説致します。

・HARファイル利用による試験シナリオ定義の簡略化
・一括実行とGroup別の実行結果の表示
・VUSごとのログイン情報の事前定義
・Cookieと動的データの利用(ログイン認証とクロスサイトスクリプティング対策による意図しないリダイレクトの抑制対応)

HARファイルの利用

HARの作成

k6 の試験シナリオを作成・実行するにあたり、1リクエストずつ JavaScriptで定義していくのではなく、ブラウザで実際に動かしたときのクロールデータを元にして実行するようにしたい場合に、HAR(HTTP アーカイブ) を利用するという手段があります。HARには HTTPリクエストとレスポンスの情報が記録されます。

HARについては k6 公式から、 Google Chrome や Firefox の拡張機能や、HARコンバーター が提供されていますが、今回は公式のツール群は使わずに Chromeの開発者ツールとテキストエディタで HARを作成します。これは、普段は使わないブラウザ拡張機能をインストールしたくないことと、ひとつのスクリプトで一括実行したいためです。

Chromeの開発者ツールでは、送受信される画像データなども含めて「コンテンツと一緒にHARとしてすべて保存」する方法も提供されていますが、この記事では簡略化のため、画像など(いわゆるメディアデータ)のテストは不要とします。ですので代わりに、右クリックメニューの「コピー」→「HARとしてすべてコピー」で、軽量なテキストデータのみで HARを作成します。

以下の手順で作成できます。

1. Chrome開発者ツールを F12 等で開き、ネットワークタブを開きます

2. 開きたい画面を開く前に、ネットワークタブの「消去」ボタンを押下して、綺麗にしておきます

※クリックで拡大します

3. 開きたい画面を開くと、ネットワーク通信が走ります

4. 通信が落ち着いた時点で、一覧部分を右クリックして「HARとしてすべてコピー」します

※クリックで拡大します

5. コピーした内容を任意のテキストエディアに張り付けて、HARファイル格納先のフォルダ(今回は ./hars )に任意のファイル名で保存します

※クリックで拡大します

※このファイル名は、結果表示時に使用されます。ファイル名にはテスト対象のURLの一部を使うのが手っ取り早いです(例えばベースURLが https://example.com/testapp/ のWebサイトの場合は https://example.com/testapp/user/ のクロールHARファイルは user.har にする等)。基本的にどのOSでもファイル名に半角スラッシュは使えません。そのため、例えば  user/create として結果表示したいクロールのHARの場合は、少し強引ですが半角スラッシュを「___(3連続の半角下線)」に置換して user___create.har として保存してください。そうすれば後述するHARファイル読込処理にて半角スラッシュへ戻されます。

試験対象URLの置換と抽出

HARファイルには、対象Webサイトのホスト名が保存されます。そのため、単純にHARファイルからURLを拾ってしまうと、HARを作成した環境でしか試験できません。

実務でよくある構成として、本番環境とは別に、開発環境やステージング環境を独立したネットワーク上に(URLも別で)用意することがありますが、同じ内容なのに環境ごとにHARを作るのは非効率です。

また別な問題として、外部URLからのリソースファイル読みこみ(jQuery, Bootstrap, GoogleMap など, CDN経由のCSSやJavaScriptの利用等)がある場合、それに対して負荷をかけてしまうとDoS攻撃と見なされる恐れがあるため、基本的には負荷試験対象から外す必要があります。

それを踏まえて今回は、HAR作成時のURL(例では harUrl )を、実際の試験対象のURL(例では targetUrl )に置換します。それと同時に、HARにharUrl以外のリクエストが記録されていた場合は、試験対象から外します。さらに、開発環境でスクリプトを作成し、そのまま別環境へテストを投げられるように、targetUrl はファイルへハードコーディングするのではなく、コマンドライン引数 URL から指定できるようにします。

HAR作成は http://localhost で行い、試験対象は http://192.168.56.10/ というローカルIP とする場合は、以下のようにします。

複数HARの一括実行

同じHARを単発で繰り返して実行した場合、キャッシュなどで性能が意図せず底上げされる可能性があります。性能が上がるのは歓迎すべきことですが、実際の運用ではリクエストが複数のURLに分散するために、単発試験時よりもキャッシュが有効に効かず、想定していたよりもパフォーマンスが悪くなる危険性があります。それを避けるため、作成したHARは今回はランダムに複数一括実行させます。

残念ながら、ES2015/ES6 のJavaScriptには、ディレクトリからファイル一覧を取得するうまい方法がなく、k6 にもその手のツールは用意されていません。npm module を使えば glob モジュールを利用することでファイル一覧の取得が可能ですが、k6 はNode.js上のモジュールではなく golang の実行系を使っているため、そのままでは npm のモジュールを使えません。現状では npm で、Webpack や browserify などを使ってモジュール化するしかないため、HARファイルの一括取得のためだけにそれを行うには手間がかかりすぎます。

それを踏まえて、今回は解決方法として、findコマンドを使ってファイル名をセミコロンで連結した文字列を生成し、k6スクリプトに環境変数で渡すことにします。

公式のリポジトリのissuesに参考例が掲載されています。リンク先には他にも、JSONでファイル一覧を作っておく方法も紹介されています。

HARファイルが ./hars 以下に aaa.har, bbb.har, ccc.har のファイル名で存在するとき、bash にて

を実行すると、文字列「aaa.har;bbb.har;ccc.har;」を生成することができます。これを利用して k6 を下記のように実行します。

なお、今回はファイル名に使えなかった半角スラッシュの代理として___があった場合、元の文字に置換することにします(前掲の testCases の以下の箇所)。

VUSユーザのログイン情報の事前定義

ログイン機能を持つWebサイトの試験の場合、Webサイト側で事前に作成されたユーザを使ってログインして試験したい場合があります。

今回は、Webサイト側のユーザ定義を users.csv というCSVファイルとしてエクスポートして使用しました。このCSVファイルには、以下のようにIDと平文のパスワードが定義してあります。

k6 の 外部ライブラリに用意されている Papa Parse を利用してパースし、SharedArray 型で保持しておきます。

SharedArray は k6 の配列的な使い方のできる省メモリのオブジェクトで、公式ドキュメントでは1000要素以上ある場合にメモリ・CPU効率が上がるとのことです。ただし const のように、初期化は1度しかできず、以降は読取専用なのでご注意ください。

k6 は、VUS(Virtual UserS)と呼ばれる無限ループが独立して iteration (繰り返し)を行います。

今回の試験対象のWebアプリケーションでは、一度ログインしたユーザはログアウトするまで再ログインはしない作りになっているため、より実運用に近づけるために、ログイン情報をVUごとにCookieへ持たせるようにします(Cookieの利用については、後ほど詳しく解説します)。

なお、VU共通の変数を持ちたい(値を変更したい)場合は、グローバルスコープで let, var で定義するのではなく、
上のコード例でも使った globalThis を使うほうが安全です( 理由についてはv0.40.0 リリースノートの Main module/script no longer pollutes the global scope をご覧ください)。

送信するフォームデータは、試験対象のシステムのログイン時のPOSTのパラメーター名にあわせる必要があります。

今回の例は、ユーザー名が email、パスワードが password 、後述のトークンが _token というパラメータキーの場合です。

Cookieと動的データの利用

ログイン時のセッションキーや、クロスサイトスクリプティング対策のためのトークンなどをCookieに持つようなWebサイトを試験する場合、何も考慮しないでリクエストを投げると、Webサイト側の認証・認可の機能でエラーページやログイン画面にリダイレクトされてしまい、意図した試験にならない場合があります。

k6 公式サイトにそれぞれ、Cookie動的データ利用についての実装例があります。前段の HARを利用する点も踏まえて、少し複雑になりますが、今回は以下のように実装しています。

①ログイン時など、レスポンスヘッダに Set-Cookie があれば VU ごとにその値を保持する
②VU ごとに、保持している Cookie がなれけば、先にログイン画面をリクエストしてログインする。Cookieが既にあればログイン画面をスキップする
③リクエストの際に、HTMLのINPUT要素でname「_token」属性を持つINPUT要素があれば、その値をリクエストヘッダ「x-csrf-token」およびリクエストパラーメーター「token」の値として設定する※
④リクエストの際に、Cookieに XSRF-TOKEN 値が設定されていれば、その値をリクエストヘッダ「x-xsrf-token」の値として設定する※

※ただしリクエストヘッダもリクエストパラメータ―も、HARでの元リクエストで、そのキーが使用されている場合のみ設定する

①は、前掲したログイン処理の51行目以降で行っています。
②も、ログイン処理の28行目のブロックです。
③と④については以下の処理で行います。

Group別の結果表示

標準のメトリクスでは check 項目ごとにグループ単位で成功率が表示されますが、その他のメトリクスはグループ単位では表示されず、全てまとめての結果しか表示できません。ですので今回は、グループ別に主要なメトリクスを表示させるため、カスタムメトリクスを利用します。

実行結果

※クリックで拡大します

 

今回は動作確認ということで負荷としては極控えめに、3VUS、5分間で実行しました。上述の通り、グループごと(HARごと)の結果が表示されています。

使用したスクリプトの完全版はこちらです。

次回は可視化ツール Grafana を利用して、リアルタイムにグループ別の実行状態を表示する方法について紹介する予定です。