イチオシな負荷テストツール k6 の始め方


k6 とは

  • Go で書かれた開発者フレンドリーな負荷テストツール
  • OSS
  • シナリオは JS で作成
  • HAR(HTTP アーカイブ) からシナリオへのコンバーターあり
  • 各種ツールとの連携が充実
  • ブラウザ含めたEtoEテストも可能
  • 公式サイト
  • Github リポジトリ
  • 公式ドキュメントが非常にわかりやすい k6 documentation


インストール

GitHub Releases page からスタンドアロンのバイナリを取得するか、以下のコマンドでインストール可能。

単一の実行ファイルだけなので、作業ディレクトリに直接実行ファイルを配備するだけでも良い。

後述の xk6-dashboard を使う場合は、xk6-dashboard Releases からプラットフォーム別のバイナリをダウンロードして任意ディレクトリに解凍すれば良い(必要に応じてパス設定)。

MacOS

$ brew install k6

Windoes

> winget install k6

Docker

$ docker pull grafana/k6

バージョン確認

$ k6 version
k6 v0.44.1 (.....)


簡単な負荷テストの例

任意ディレクトリに script.js として以下を作成する。

import http from 'k6/http';
import { sleep } from 'k6';

export default function () {
  http.get('https://www.google.com/');
  sleep(1);
}

スクリプトの実行には run コマンドを使用する。

$ k6 run script.js

仮想ユーザ vus(VUs) と期間 duration を指定する場合は以下

$ k6 run --vus 10 --duration 15s script.js

10ユーザ並列で、15秒間リクエストを繰り返す。

実行結果はいかのようになる。

$ k6 run --vus 10 --duration 15s script.js

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: script.js
     output: -

  scenarios: (100.00%) 1 scenario, 10 max VUs, 45s max duration (incl. graceful stop):
           * default: 10 looping VUs for 15s (gracefulStop: 30s)


     data_received..................: 2.8 MB 173 kB/s
     data_sent......................: 29 kB  1.8 kB/s
     http_req_blocked...............: avg=9.45ms   min=0s      med=0s       max=132.93ms p(90)=0s       p(95)=132.41ms
     http_req_connecting............: avg=41.14µs  min=0s      med=0s       max=623.19µs p(90)=0s       p(95)=505.2µs
     http_req_duration..............: avg=107.29ms min=91.93ms med=103.77ms max=207.01ms p(90)=118.85ms p(95)=126.32ms
       { expected_response:true }...: avg=107.29ms min=91.93ms med=103.77ms max=207.01ms p(90)=118.85ms p(95)=126.32ms
     http_req_failed................: 0.00%  ✓ 0        ✗ 140
     http_req_receiving.............: avg=10.56ms  min=246.3µs med=7.63ms   max=113ms    p(90)=16.97ms  p(95)=20.08ms
     http_req_sending...............: avg=206.1µs  min=0s      med=0s       max=1.06ms   p(90)=730.6µs  p(95)=982.05µs
     http_req_tls_handshaking.......: avg=7.7ms    min=0s      med=0s       max=108.49ms p(90)=0s       p(95)=107.86ms
     http_req_waiting...............: avg=96.52ms  min=74.78ms med=94.34ms  max=172.09ms p(90)=107.71ms p(95)=112.56ms
     http_reqs......................: 140    8.814585/s
     iteration_duration.............: avg=1.13s    min=1.09s   med=1.11s    max=1.3s     p(90)=1.13s    p(95)=1.3s
     iterations.....................: 140    8.814585/s
     vus............................: 10     min=10     max=10
     vus_max........................: 10     min=10     max=10


running (15.9s), 00/10 VUs, 140 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  15s


出力メトリクス

代表的なものは以下

  • http_req_duration 全リクエストの合計レイテンシー。上記例では平均 107.29ms
  • http_req_failed 失敗リクエストの数(エラー率, 可用性)。上記例ではゼロ
  • http_reqs リクエスト数。上記例では秒間8.8リクエスト
  • iterations イテレーション数。上記例ではシナリオを合計140回実行

その他、カスタムメトリクスを定義して収集することもできる。


テスト結果の詳細は、以下のように外部ファイルに出力することができる。

$ k6 run script.js --out csv=test.csv

--out influxdb=http://localhost:8086/k6 のように外部サービスにメトリクスを出力することもできる(Amazon CloudWatch, Datadog など多数の組み込みサポートがある)。大規模なテストで、複数ノードでテストを実行する場合にテスト結果を集約できる。


xk6-dashboard

k6 にビジュアライゼーション機能を追加した xk6-dashboardも良い。

リリースページからバイナリをダウンロードして解凍すると、k6(k6.exe) という単一の実行ファイルが得られる。

この実行ファイルにダッシュボードのサーバが組み込まれているため、実行すればブラウザでSSE経由でリアルタイムにメトリクスを見ることができる。

ダッシュボード利用時には --out dashboard を指定して実行する。

$ ./k6 run --out dashboard script.js

http://127.0.0.1:5665 にアクセスすれば、ダッシュボードでメトリクスをリアルタイムで確認できる。


テストスクリプトの構成

スクリプトは、トップレベルに初期化コードを記載する。

export default function に、イテレーションで実行するコードを記載する。 この関数がテスト期間中に繰り返して実行される。

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

// init code  仮想ユーザ(VU)毎に1回実行
export const options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m30s', target: 10 },
    { duration: '20s', target: 0 },
  ],
};

// VU code  イテレーション毎に実行
export default function () {
  const res = http.get('https://...');
  check(res, { 'status was 200': (r) => r.status == 200 });
  sleep(1);
}

VU をまたいで共有するデータの初期化は setup() 関数、テスト終了時の後処理(例えばテスト完了をwebhookなどで通知)は teardown() 関数に定義する。

setup() から値を返すことで、その内容を VU コードや teardown() で引数として受け取ることができる。

import http from 'k6/http';

export function setup() {
  const res = http.get('https://httpbin.test.k6.io/get');
  return { data: res.json() };
}

export default function (data) {
  console.log(JSON.stringify(data));
}

export function teardown(data) {
  console.log(JSON.stringify(data));
}

group を用いて複数のリクエストを纏めることがでる。

import { group } from 'k6';

export default function () {
  group('visit product listing page', function () {
    // ...
  });
  group('add several products to the shopping cart', function () {
    // ...
  });
}

同じグループのリクエストにはグループ名でタグ付けされ、結果出力に反映される。


実行オプション

実行オプションは init code として以下のように定義する。

import http from 'k6/http';
// ...

export const options = {
  userAgent: 'MyK6UserAgentString/1.0',
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m30s', target: 10 },
    { duration: '20s', target: 0 },
  ],
  thresholds: {
    http_req_failed: ['rate<0.01'], // http errors should be less than 1%
    http_req_duration: ['p(95)<200'], // 95% of requests should be below 200ms
  },
};

export default function () {
  http.get('http://...');
}

stages を定義することで、仮想ユーザ(VUs) を時間の経過に応じて段階的に変化させることができる。

export const options = {
  stages: [
    { duration: '30s', target: 20 },
    { duration: '1m30s', target: 10 },
    { duration: '20s', target: 0 },
  ],
};

コマンドラインで指定した場合、コマンドラインのオプションが優先される。

よく使うであろうオプションには以下がある。

  • insecureSkipTLSVerify: true TLS 検証をスキップ(オレオレ証明書)
  • maxRedirects: 10 リダイレクト回数を制限
  • noCookiesReset: true VUイテレーションでCookieをリセットしない
  • userAgent: 'k6/1.0' ユーザエージェントを指定
  • thresholds メトリクスの対して閾値を指定し、エラーとして扱う
  • scenarios スクリプト中に複数の条件のテストシナリオを定義

その他、Options reference を参照。


HTTP リクエスト

http.get() http.post() http.put() など、HTTPメソッドに応じたメソッドにてリクエストを発行する

import http from 'k6/http';

export default function () {
  let id = '999'
  http.get(`http://example.com/posts/${id}`);
}

集計用に、リクエストにタグを付けることができる。

http.get(`http://example.com/posts/${id}`, {
  tags: { name: 'PostsItemURL' },
});

POSTリクエストの場合は以下のようになる。

export default function () {
  const url = 'http://...';
  const payload = JSON.stringify({
    email: 'aaa',
    password: 'bbb',
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  http.post(url, payload, params);
}


HTTP レスポンスのCheck

以下のようにHTTP 応答コードの検証が可能。

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

export default function () {
  const res = http.get('...');
  check(res, {
    'is status 200': (r) => r.status === 200,
  });
}

応答本文の検証は以下

const res = http.get('...');
check(res, {
'verify homepage text': (r) =>
  r.body.includes('Collection of simple web-pages suitable for load testing'),
});

以下のように複数指定することもできる。

const res = http.get('...');
check(res, {
'is status 200': (r) => r.status === 200,
'body size is 11,105 bytes': (r) => r.body.length == 11105,
});

response.html([selector]) でHTML要素を取得することもできる。

check(res, {
'caption is correct': (r) => r.html('h1').text() == 'Example',
});

selector には jquery.find で指定するセレクタと同様に指定する。

以下の様に処理することもできる。

const doc = res.html();
doc
.find('link')
.toArray()
.forEach(function (item) {
  console.log(item.attr('href'));
});

詳細は k6 API を参照。


スクリプトの自動生成

Webブラウザから出力した HAR ファイル (HTTP ARchive) からスクリプトを自動生成できる。

Github リポジトリは  har-to-k6 converter

npm パッケージとして公開されているため、npm i でインストールする。

$ npm install har-to-k6

HAR ファイルと出力先を指定して実行することで、k6 のスクリプトが得られる。

$ npx har-to-k6 <har.har> -o <script.js>


HAR ファイルはブラウザの開発者ツールから取得する。

Chrome の場合、「デベロッパーツール」-「ネットワーク」で画面操作し、一覧を右クリックし、「コピー」-「HARとしてすべてコピー」でHARのJSONがコピーできる。

Firefoxの場合「ウェブ開発ツール」-「ネットワーク」で画面操作し、一覧を右クリックし、「値をコピー」-「HAR形式ですべてコピー」でHARのJSONがコピーできる。

コピーしたJSONをHAR ファイルとして保存し、k6スクリプトへコンバートすれば、ブラウザで操作した内容がスクリプト化できる(実際には必要箇所を加工して使うことになる)。