本チュートリアルの内容:

  1. k6 の概要と使い方
  2. k6 を使用してパフォーマンス テストを作成、実行する方法
  3. k6 パフォーマンス テストの結果を分析する方法

パフォーマンス テストとは、システムにさまざまな負荷を掛けながらパフォーマンスを計測するテストであり、 特に安定性と応答性を重点的にテストします。 パフォーマンス テストを行うことで、システムの堅牢性と信頼性だけでなく、具体的な限界点も知ることができます。 このチュートリアルでは、Heroku プラットフォームでホストされるシンプルな API を対象として、k6 により負荷テストを実施する方法について説明します。 また、テスト結果の解釈の仕方もあわせて解説します。

前提条件

このチュートリアルを進めるには、以下の準備が必要です。

  • テストの基礎知識
  • GitHub アカウントを用意する
  • CircleCIアカウントを用意する
  • k6 Cloud アカウントを用意する

本チュートリアルはすべての CI/CD プラットフォームに対応していますが、サンプルとして CircleCI を使用します。 CircleCI アカウントをお持ちでない場合は、こちらから無料アカウントを作成してください

K6とは?

k6 は、パフォーマンス テストを快適に行えるオープンソースのフレームワークです。 使いやすさとパフォーマンスを両立しているだけでなく、スクリプトを実行可能なコマンドライン インターフェースやテスト結果を可視化できるダッシュボードなどのツールも備えているため、高い注目を集めています。

k6のロゴ

k6 は goja というプログラミング言語で開発されています。goja とは、JavaScript ES2015 (ES6) を純粋な Go 言語で実装したものです。 つまり、k6 のスクリプトは JavaScript で書けるということです。ただし、JavaScript ES2015 以外の言語構文は使用できません。

注: k6 のエンジンは Node.js ではありません。 Go 言語版 JavaScript コンパイラを使用しています。

k6 の概要がわかったところで、さっそく k6 でパフォーマンス テストを実行する準備に取り掛かりましょう!

リポジトリをクローンする

まずは、以下のコマンドを実行して、こちらの GitHub リポジトリからサンプル アプリケーションをクローンします。

git clone https://github.com/CIRCLECI-GWP/api-performance-testing-with-k6.git

次は、マシンに k6 をインストールしましょう。

k6 をインストールする

JavaScript モジュールとは異なり、k6 はパッケージ マネージャーでインストールする必要があります。 macOS なら Homebrew、 Windows OS なら Chocolatey を使用してください。 k6 の公式ドキュメントでは他の OS 用のインストール方法も紹介されていますが、 今回は macOS 用のインストール手順に従うことにします。 次の Homebrew コマンドを実行します。

brew install k6

このコマンドを実行すれば、k6 テストを作成する準備は完了です。

1 つ目のパフォーマンス テストを作成する

おつかれさまでした! これで、k6 の概要を知り、インストールすることができました。 次は、k6 で計測できるデータと、最初の負荷テストを作成するためのメトリクスについて学びましょう。 負荷テストでは、システムに複数のユーザーが同時にアクセスする場合をシミュレーションすることで、想定されるシステムの使用状況をモデル化します。 今回は、一定期間内に複数のユーザーが API にアクセスする想定でシミュレーションしてみましょう。

アプリケーションの負荷テストを作成する

今回の負荷テストでは、API システムにさまざまなユーザー グループからのアクセスがあった場合のシステムの応答性と安定性を計測します。 このようなテストを行うことで、システムの限界点と許容限度がわかります。

このチュートリアルでは、Heroku 上にホストされている API アプリケーションで ToDo 項目を作成する k6 テスト ファイルを基に、複数のテスト スクリプトを作成します。 負荷テストでは、仮想ユーザー数やリクエスト数、時間などの要素の増減が必要です。 今回は、アプリケーションにアクセスするユーザー数を増減させながら、テストを実行してみましょう。 この機能は k6 フレームワークに付属しているので、実装はとても簡単です。

ユーザー数を増減させるために、k6 の Executor という機能を利用します。 Executor とは k6 の実行エンジンの動力であり、スクリプトの実行シナリオを管理する役目を担います。 Executor では、主に次の要素を設定します。

  • 追加するユーザーの数
  • 実行するリクエストの数
  • テストの実行時間
  • テスト アプリケーションに送信するトラフィック

最初のテストでは、k6 のランプアップというアプローチと k6 Executor を組み合わせます。 k6 のランプアップ アプローチでは、まず、スクリプトを実行する仮想ユーザー (VUS) の数をピーク値まで徐々に増やします (ランプアップ)。 その後、指定期間にわたり VUS 数を徐々に減らしていき、テストの実行を終了します (ランプダウン)。

ここまで説明した Executor、仮想ユーザー、およびテスト自体のアイデアをまとめた負荷テストを作成してみましょう。 以下に例を示します。

import http from 'k6/http';
import { check, group } from 'k6';

export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // 0.5 分間にわたり仮想ユーザーが 1 名から 3 名にランプアップしたときのトラフィックをシミュレーションする
       { duration: '0.5m', target: 4}, // 0.5 分間、仮想ユーザーを 4 名に維持する
       { duration: '0.5m', target: 0 }, // 仮想ユーザーを 0 名にランプダウンする
     ],
};

export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });

   let todoID;
   group('Create a Todo', () => {
       const response = http.post('https://todo-app-barkend.herokuapp.com/todos/',
       {"task": "write k6 tests"}
       );
       todoID = response.json()._id;
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
       check(response, {
           "response should have created todo": res => res.json().completed === false,
       });
   })
});

このスクリプトでは、options オブジェクトで、指定した各ステージにおけるユーザー数の経時変化を定義しています。 Executor を明示的に指定することはしていません。しかし、k6 は options オブジェクト内で stagesdurationstargets が使用されていることを認識し、Executor を ramping-vus に設定します。

この例では、1 分 30 秒の期間における同時アクセス ユーザー数の上限を 4 名に設定しています。 そのため、スクリプトが実行されると、k6 はアプリケーション ユーザーの数を徐々に増やしながら、指定期間内で create-todo リクエストのイテレーションをできるだけ多く実行しようとします。

言い換えると、指定期間内において、指定ステージごとにアクティブな仮想ユーザーの数を変えながら可能な限り多くのリクエストを実行するということです。 このテストの成否は、指定期間内にアプリケーションがどれだけのリクエストを正常に完了できたかで判定します。 下図に、時間ごとの仮想ユーザー数の変化を示します。

ランプアップ グラフ

上図のランプアップ グラフから、負荷テストの仮想ユーザー数が、ピークに達するまで時間とともに徐々に増えていくことがわかります。 リクエストを完了した仮想ユーザーはシステムを離れていき、最終ステージで指定した人数 (0) になります。 このチュートリアルでは、ユーザー数のピークは 4 に指定しています。 なお、負荷テストに役立つ k6 Executor は ramping-vus だけではありません。 k6 には、他にもさまざまな Executor が用意されています。 どの Executor が適しているかは、実行するパフォーマンス テストのニーズや性質によって異なります。

前述のサンプル スクリプトを振り返ると、k6 ではテストの内容やマシン上で実行する仮想ユーザーの数を定義するだけでなく、アサーションも作成できることがわかります。具体的には、group() ブロック内で check() を使用し、さまざまなアサーションを作成しています。

注: k6 のgroup (グループ) を使用すると、テスト結果の分析用に大きなテスト スクリプトをまとめることができます。 また、check (チェック) はグループ内のアサーションです。ただし、k6 のチェックは他のアサーションと異なり、成否にかかわらず負荷テストの実行を中断することはありません。

k6 を使用してパフォーマンス テストを実行する

これで、システムやアプリケーションのパフォーマンスを測定するテストの実行準備が整いました。 さっそく、Heroku の無料版 Dyno (Heroku のコンテナ) の限界、および Dyno にホストした ToDo API アプリケーションの限界をテストしてみましょう。 無料版 Dyno は、負荷テストの実行環境として最適なサーバーとは言えませんが、システムが限界点に達するタイミングや状況を知るサンプルとしては十分です。 このチュートリアルでは、Heroku と API アプリケーションのいずれかが限界点に達すると想定しています。 初めのテストでは、仮想ユーザー (VUS) から Heroku に対して HTTP リクエストを複数実行します。 初めの VUS の人数は 1 名として、4 名まで徐々に増やします。各 VUS は、1 分 30 秒の期間内に ToDo 項目の作成を行います。

注: このチュートリアルでは、API を操作するユーザー セッションを Heroku が 4 つ同時に処理できると仮定しています。

それでは、次のコマンドをターミナルで実行して、上述のコード スニペットで作成したパフォーマンス テストを実行しましょう。

k6 run todos-testing.js

テストが完了すると、初めての k6 テストの実行結果が返されます。

k6 テストの実行結果

返された結果を見ると、パフォーマンス テストの実行に 1 分 30.3 秒かかり、テストの全イテレーションが成功しています。 また、テストの実行期間において、4 名の仮想ユーザーが計 390 回のイテレーション (ToDo 項目作成プロセス) を行ったこともわかります。

返された結果には、他にも次のメトリクスがあります。

  • checks: テストで宣言した check() アサーションのうち、実際に完了した数 (今回は 100% が成功)
  • http_reqs: 送信された HTTP リクエストの総数 (今回は 780 件)
  • ToDo 項目作成のイテレーションの総数と Heroku サーバーの稼働確認用 HTTP リクエストの数

初めてのテストは無事に成功し、アプリケーションについて有用な情報が得られました。 しかしこのテストだけでは、システムに限界点があるかどうか、つまり、API を同時に利用するユーザー数が一定値を超えるとアプリケーションの挙動がおかしくなるかどうかということはわかりません。 というわけで、限界点を知るためのテストを作成することにしましょう。

まず、Heroku サーバー、ひいてはアプリケーション自体を壊すために、HTTP リクエストを増やしてみます。 さらに、リソースを節約するために、仮想ユーザー数とテストの実行時間を減らします。 こうした調整は、テスト ファイルの設定セクション (export let options ブロック) 内にある後ろ 2 つのテスト ステージをコメントアウトするだけで行えます。

注: 「アプリケーションを壊す」とは、実行が成功しなくなりエラーが返されるだけの負荷をシステムに掛けるということです。リソースやアプリケーション コードを変更するわけではありません。 つまり、ユーザー数やリクエスト数だけを変更して、システムからエラーが返されるようにするのです。

stages: [
       { duration: '0.5m', target: 3 }, // 0.5 分間にわたり仮想ユーザーが 1 名から 3 名にランプアップしたときのトラフィックをシミュレーションする
       // { duration: '0.5m', target: 4 }, -> このステージは実行しないのでコメントアウト
       // { duration: '0.5m', target: 0 }, -> このステージは実行しないのでコメントアウト

上記の設定により、テストの実行時間を短縮し、テストを実行する仮想ユーザーの数を減らすことができました。 次は、以下の HTTP リクエストを先ほどのパフォーマンス テストに追加します。

  • 作成した ToDo 項目を取得する
  • 取得した ToDo 項目の todoID が作成時のものと同じか確認する
  • 取得した ToDo 項目の作成状態 (state) が completed: false であるか確認する

このようなパフォーマンス テストを実行するためのテスト ファイルとして、create-and-fetch-todo-http-request.js をご用意しています。このファイルは、初めにクローンしたリポジトリのプロジェクト ディレクトリのルートに配置されています。 それでは、ターミナルで次のコマンドを実行しましょう。

k6 run create-and-fetch-todo-http-request.js

コマンドの実行が完了すると、下図のようなテスト結果が返されます。

K6 テスト結果

返された結果から、今回のテストでは、アプリケーションで使用している無料版 Dyno に対して限界を超える負荷が掛かったことがわかります。 リクエストは成功していますが、一部のリクエストでは時間内に応答が返されていません。 そのため、一部のリクエストが失敗しています。 同時仮想ユーザー数を 3 名に減らし、実行時間を 30 秒に縮めた結果、完了したイテレーションの数は 56 になりました。 また、この 56 件のうち、成功した check の割合は 95.97% でした。

check が失敗した原因は、作成した ToDo 項目の todoID を取得する際の応答でした。 これで、API、あるいはシステムにボトルネックが存在することが確かめられました。 最後に、http_reqs を見てみましょう。値が 168 となっていますが、これは ToDo 項目の作成、ToDo 項目の取得、Heroku サーバーの稼働確認の各プロセスで返されたリクエストの総数です。

56 リクエスト (イテレーションごとの件数) * 3 (リクエスト プロセスの総数)

これまでのテスト結果からわかるように、パフォーマンス テストのメトリクスは、どのシステムやアプリケーションでも一律というわけではありません。 得られるメトリクスの数値は、マシンの構成、アプリケーションの性質、さらにはテスト対象アプリケーションの従属システムに左右されます。 パフォーマンス テストの目的は、ボトルネックを特定して修正することです。 使用量の急増や平均を超えるリソース需要など、システムでどのような負荷に対処できるかを把握して、将来の計画を立てなければなりません。

先ほどの手順では、Heroku の無料版 Dyno でホストしているアプリケーションに対してパフォーマンス テストを実施し、CLI に返された結果を分析しました。 しかし k6 では、K6 Cloud ダッシュボードを利用して、パフォーマンス テストの結果を分析することもできます。 このダッシュボードは、パフォーマンス テストの結果を可視化する Web ベースのインターフェースです。

k6 Cloud の構成と出力

ターミナル上で k6 パフォーマンス テストを実行し出力を受け取るだけでも、十分に役立ちます。しかし、チーム内で結果や出力を共有できればもっと便利でしょう。 k6 Cloud ダッシュボードには素晴らしい分析機能が多数用意されていますが、特にデータ共有は最高です。

それでは、k6 Cloud ダッシュボードを構成するために、事前に用意したアカウントで k6 Cloud にログインします。 [API token (API トークン)] を選択します。

k6 クラウド API トークン

[API token (API トークン)] ページに移動したら、k6 Cloud でのテストの実行やアップロードに使用する API トークンをコピーします。

次に、K6_CLOUD_TOKEN 環境変数を追加します。 以下のコマンドをターミナルで実行して、先ほどコピーした k6 Cloud トークンを設定します。

export K6_CLOUD_TOKEN=<k6-cloud-token>

注: コードの は、先ほど K6 Cloud ダッシュボードでコピーした API トークンに置き換えてください。

これで、負荷テストを実行する準備ができました。 次のコマンドを実行しましょう。

k6 run --out cloud create-and-fetch-todo-http-request.js

--out cloud は、k6 にクラウドへ結果を出力するよう指示するオプションです。 テストが始まると、k6 Cloud 上でテスト結果を示すダッシュボードが自動的に生成されます。 このダッシュボードで、パフォーマンス テストの結果を評価できます。

ダッシュボードを利用することで、ターミナルで出力を見るよりも結果を解釈しやすく、かつ共有しやすくなります。 アップロードしたテスト実行結果のメトリクスのうち、まず確認できるのは、リクエストの総数と仮想ユーザーごとのリクエストの実行結果です。 また、各ユーザーがリクエストを実行したタイミングについての情報もわかります。

全リクエストの平均応答時間の比較グラフ

上図のグラフには、仮想ユーザーの総数と実行したリクエストの総数、全リクエストの平均応答時間が比較して示されています。 各リクエストの詳細を見られれば、さらに有用な情報が得られそうですね。 k6 Cloud ダッシュボードでは、groups()checks() を利用することでリクエストの詳細を確認できます。

それでは、[Checks (チェック)] タブを選択し、ビューを [TREE (ツリー)] に切り替えて結果をフィルタリングしてみましょう。

k6 パフォーマンス insight checks

今回のテストで行われたすべてのリクエストが時系列順に並べられ、失敗したリクエスト数、リクエストの成功率、全リクエストの平均所要時間、実行されたリクエストの総数が示されます。

リクエストの詳細と実行時間を調べるには、このページ上にある [HTTP] タブを選択します。 これで、実行された全リクエスト、各リクエストの実行時間、およびリクエストどうしの比較結果を確認できます。 たとえば、あるリクエストは 95 パーセンタイル、別のリクエストは 99 パーセンタイルに属しています。 また、実行に最も時間がかかったリクエストには、参考として標準偏差も示されます。 このダッシュボードを利用することで、ボトルネックの特定に役立つあらゆるデータポイントを確認できます。

k6 performance insight http リクエスト

さらに、k6 Cloud ダッシュボードでは、特定のリクエストを選択し、平均実行時間とパフォーマンスを比較することもできます。 この比較機能は、データベースでリソースに複数のユーザーからのアクセスがあったために多数のリソースがロックされ、ボトルネックが生じた場合などの問題特定に役立ちます。

このように、k6 Cloud ダッシュボードは、個人だけでなくチーム全体にとっても多くのメリットを備えています。

次は、CircleCI で k6 パフォーマンス テストをトリガーし、デプロイのたびにテストが実行されるように構成してみましょう。

Git をセットアップして CircleCI にプッシュする

注: プロジェクト リポジトリをクローンしている場合は、このセクションは飛ばしてかまいません。 このセクションは、CircleCI プロジェクトのセットアップ方法を説明するだけのものです。

CircleCI をセットアップするために、以下のコマンドを実行してプロジェクトの Git リポジトリを初期化します。

git init

次に、ルート ディレクトリに .gitignore ファイルを作成します。 このファイル内に node_modules を追加して、npm で生成されたモジュールがリモート リポジトリに追加されないように設定します。 コミットを追加してから、GitHub にプロジェクトをプッシュします。

CircleCI にログインして、[Projects (プロジェクト)] ダッシュボードに移動します。 ここでは、GitHub のユーザー名または組織に関連付けられている GitHub リポジトリの一覧から、セットアップするリポジトリを選択できます。 このチュートリアルでは、api-performance-testing-with-k6 という名前のプロジェクトを使用します。 [Projects (プロジェクト)] ダッシュボードで、目的のプロジェクトの [Set Up Project (プロジェクトのセットアップ)] を選択して、 既存の設定ファイルを使用するオプションを選択します。

注: ビルドの開始後、パイプラインは失敗します。 プロジェクトを適切にビルドするには、カスタマイズした .circleci/config.yml 設定ファイルを GitHub に追加する必要があるからです。

CircleCI をセットアップする

ルート ディレクトリに .circleci ディレクトリを作成し、config.ymlファイルを追加します。 この設定ファイルに、すべてのプロジェクト用の CircleCI 設定を記述します。 今回は以下に示すように、CircleCI の k6 Orb を使用して k6 テストを実行します。

version: 2.1
orbs:
 k6io: k6io/test@1.1.0
workflows:
 load_test:
   jobs:
     - k6io/test:
         script: create-todo-http-request.js

サードパーティ製 Orb を使用するには

CircleCI Orb とは、構成コードを 1 行にまとめた再利用可能な YAML 構成パッケージです。 ただし、python@1.2 などのサードパーティ製 Orb を使用するには、以下のいずれかの手順を実施する必要があります。

  • 組織設定でサードパーティ製 Orb を許可する (管理者である場合)
  • 組織の CircleCI 管理者にサードパーティ製 Orb の許可を依頼する

これで、ビルドが完了し、k6 のパフォーマンス テストが正常に実行されたこと、および CircleCI に k6 が統合されたことを確認できました。

CircleCI に k6 が統合

最後に、各エンドポイントの実行時間を計測するためのメトリクスを追加してみましょう。

API リクエストの実行時間を評価する

これまでのパフォーマンス テストの結果から、テストの構造よりも、ある時点におけるテスト負荷に対するシステムの応答の方が重要であることがはっきりしました。 k6 には Trend (トレンド) というメトリクス機能が搭載されており、コンソールとクラウド ダッシュボードでメトリクスをカスタマイズできます。 それでは、Trend を使用してタイミングを定義し、エンドポイントに対してリクエストが実行された時刻を確認してみましょう。 具体的には、以下のサンプル コードのように記述します。

import { Trend } from 'k6/metrics';

const uptimeTrendCheck = new Trend('/GET API uptime');
const todoCreationTrend = new Trend('/POST Create a todo');


export let options = {
   stages: [
       { duration: '0.5m', target: 3 }, // VUS が 0 名から 3 名までランプアップしたときのトラフィックをシミュレーションする
   ],
};

export default function () {
   group('API uptime check', () => {
       const response = http.get('https://todo-app-barkend.herokuapp.com/todos/');
       uptimeTrendCheck.add(response.timings.duration);
       check(response, {
           "status code should be 200": res => res.status === 200,
       });
   });

ここでは、k6 の Trend を実装するために、k6/metrics からインポートし目的のトレンドを定義しています。 今回のチュートリアルでは、稼働チェック時または ToDo 項目の新規作成時における API の応答時間を確認すれば十分です。 トレンドを作成したら、該当するテストのセクションで、定義したトレンドに必要なデータを埋め込みます。

以下のコマンドを実行して、ToDo 項目作成のパフォーマンスをテストするファイルを実行しましょう。

k6 run create-todo-http-request.js

コンソールに結果が表示されました。 なお、今回のテスト ファイルは、クローンしたリポジトリのルート ディレクトリにあります。 ローカル環境でテストを無事に実行できたら、GitHub にファイルをコミットしてプッシュしましょう。

追加したトレンド

テストの実行が完了したら、先ほど追加したトレンドの内容を確認してください。 各トレンドに、実行時間の平均値、最大値、最小値、パーセンタイル値など、各リクエストのタイミング情報が示されています。

おわりに

このチュートリアルでは、k6 の概要と、k6 を使用してパフォーマンス テストを実行する方法について説明しました。 具体的には、Heroku サーバーへの ToDo 項目の作成について、シンプルな k6 テストを作成するプロセスを学びました。 また、k6 パフォーマンス テストの結果を、コマンドライン ターミナルとクラウド ダッシュボードのそれぞれで確認し読み解く方法もご紹介しました。 締めくくりとして、カスタム メトリクスと k6 のトレンド機能の使い方も学びました。 ぜひ、このチュートリアルをチームに広め、今回学んだ内容を基に学習を積み重ねていきましょう。

このチュートリアルは作成しがいのあるものでしたので、みなさんにも楽しんでいただけたなら幸いです。


Waweru Mwaura 氏は品質工学を専門とするソフトウェア エンジニアです。生涯学習の実践者という顔も持ち、 Packt で執筆者を務めながら、工学や金融、テクノロジーの書籍を愛読しています。 Mwaura 氏の詳細な情報は、こちらの Web プロフィールをご覧ください。