ローソンデジタルイノベーション テックブログ

ローソンデジタルイノベーション(LDI)の技術ブログです

JMeter に疲れてしまった。。。そんなあなたに k6

ご無沙汰しています。LDIで開発しているOKです。

パフォーマンスツールの定番というと JMeter あたりが割と有名で使われている人が多いのかなと思いますが、 設定が大変でちょっとしたテストをしようと思ってもサクッとできない印象があるのですよね(個人の感想です)。

そこで今回紹介するのは、オープンソースの k6 という負荷テストツールです。

k6 の特徴としては、負荷試験のシナリオをJavaScriptで記述することです。 スクリプトコードで書けるので、API実行前に何か処理したり、レスポンスを受けてから何か実施したり、 API①のレスポンスを使って、API②のパラメータにしたりと 多少複雑な事も比較的簡単に実現可能なところが個人的には気に入った点です。

開発環境

本記事は以下の環境で作成しています。

  • Mac Book Pro (Intel製チップ)
  • macOS Monterey
  • Docker version 20.10.17

k6 環境構築

まず k6 の実行環境を作っていきます。 Docker を利用して構築します。こうしておけば、ファイルを配布すれば誰でもサクッと環境構築できるので便利ですね。

とりあえず以下の様なディレクトリ構成とします。

k6Demo/
  - docker-compose.yml
  - src/
    - get.js
  - env/
    - staging.yml
    - production.yml    

docker-compose.yml は次のように記述します。

version: '3'
services:
  k6:
    image: loadimpact/k6:latest
    command: run
    volumes:
      - ./src:/src
      - ./output:/output
    environment:
      ENV: "Docker環境"
      HOST: "http://host.docker.internal:8080"

environment と環境変数を設定しておくと、スクリプトコードでこの値を取得できるので、 ローカル環境、Staging環境、本番環境で値を切り替えるなんて事ができます。 例えば、staging.yml は次の様に記述しておいて、後述する 環境を指定した実行方法environment を上書きして実行する事が可能です。

version: '3'
services:
  k6:
    environment:
      ENV: "Staging環境"
      HOST: "https://hoge.example.com"

k6 のスクリプト作成

それではシナオリのスクリプトを書いていきます。 HTTP GET のAPIに対してリクエストする場合は以下の様に書けます。

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

// 1. 初期化
// 2. API実行前の処理
export function setup() {
    console.log(__ENV.ENV)
}

// 3. API実行
export default function() {
    group('GetGroup', getAPI);
}

export function getAPI() {
    const res = http.get(__ENV.HOST + `/k6demo/get`,
        {
            tags: {
                name: "GetAPI",
            }
        });
    const checkStatus = check(res, {
        'status was 200': (r) => r.status === 200,
    });
    if (!checkStatus) {
        return;
    }
    const checkBody = check(res, {
        'body was OK': (r) => r.json().result === 'GET',
    });
    if (!checkBody) {
        return;
    }

    sleep(1);
}

export function setup() としている所は読んで字のごとくセットアップに用いるのですが、 k6 がテスト実施の最初に1度だけ呼んでくれる関数になるので、ここで何かしら初期化だったりをすると良いでしょう。

負荷テストとして繰り返し実行する処理としては、 export default function() としている部分になります。 今回は、 GetGroup というグループ名をつけて、 function getAPI() を呼んでいるので、こちらがメインの処理になります。

APIのコールは16〜21行目で実装しています。難しいところはないと思いますが、今回 tags を設定して、APIの呼び出しにタグを設定しています。 タグの利用に関しては、複数のAPIで負荷試験を実施する際の識別に役立つのですが、それはまた別の機会に説明できればと思うので、今回は割愛します。

22〜27行目は、レスポンスのHTTPステータスのチェック、28〜33行目はレスポンスボディのチェックをしています。 APIの仕様によって、ここで呼び出しが成功しているか確認することで、負荷テスト時の成功率をみる事ができるようになります。

k6 実行

それでは負荷テストを実行していきます。 とりあえず一番単純な負荷の掛け方で実行するにはコマンドラインツールで以下のようなコマンドを打ちます。

docker-compose -f docker-compose.yml run --rm k6 run --out csv=/output/result.csv /src/get.js --vus 10 --duration 1m

これは、 get.js を 10の vus(virtual users (同時接続数)) で 1分間実施している形になります。
また、 csv で実施結果の詳細を出力します。 負荷の掛け方や、出力の方法は他にも色んなやり方がありますが、今回は割愛します。

実行すると、負荷試験が始まり、実施が終わると結果のサマリをコンソールに出力してくれます。


          /\      |‾| /‾/   /‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / _____ \  |_| \_\\___/ .io

  execution: local
     script: /src/get.js
     output: csv (/output/result.csv)

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

INFO[0000] Docker環境                                      source=console

running (02.0s), 00/10 VUs, 20 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  2s

     █ setup

     █ GetGroup

       ✓ status was 200
       ✓ body was OK

     checks.........................: 100.00% ✓ 40       ✗ 0   
     data_received..................: 2.8 kB  1.4 kB/s
     data_sent......................: 2.0 kB  1.0 kB/s
     group_duration.................: avg=1s       min=1s       med=1s      max=1.01s    p(90)=1.01s    p(95)=1.01s   
     http_req_blocked...............: avg=2.88ms   min=1.2µs    med=1.84ms  max=6.61ms   p(90)=6.55ms   p(95)=6.6ms   
     http_req_connecting............: avg=788.2µs  min=0s       med=467µs   max=2.18ms   p(90)=1.92ms   p(95)=2.04ms  
     http_req_duration..............: avg=2.06ms   min=878.57µs med=2ms     max=3.65ms   p(90)=3.09ms   p(95)=3.27ms  
       { expected_response:true }...: avg=2.06ms   min=878.57µs med=2ms     max=3.65ms   p(90)=3.09ms   p(95)=3.27ms  
     http_req_failed................: 0.00%   ✓ 0        ✗ 20  
     http_req_receiving.............: avg=40.27µs  min=8.26µs   med=18.01µs max=222.76µs p(90)=98.66µs  p(95)=105.54µs
     http_req_sending...............: avg=197.78µs min=4.74µs   med=88.79µs max=784.36µs p(90)=646.94µs p(95)=738.76µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s      max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=1.83ms   min=837.37µs med=1.77ms  max=3.15ms   p(90)=2.67ms   p(95)=2.78ms  
     http_reqs......................: 20      9.918911/s
     iteration_duration.............: avg=959.13ms min=79.49µs  med=1s      max=1.01s    p(90)=1.01s    p(95)=1.01s   
     iterations.....................: 20      9.918911/s
     vus............................: 10      min=10     max=10
     vus_max........................: 10      min=10     max=10

環境を指定した実行方法

前述しましたが、環境を変更して実施する場合は、 -f オプションで、環境別の設定ファイルを指定してあげれば切り替えることができます。

docker-compose -f docker-compose.yml -f env/staging.yml run --rm k6 run --out csv=/output/result.csv /src/get.js --vus 10 --duration 1m

結果の見方

サマリ

色々ありますが、 checkshttp_req_duration を見るのが良いかと思います。

checks はリクエストの成功率、成功数、エラー数を出しています。 成功かどうかの判定は、前述のソースにある 22〜31行目で実装していた check 処理によって判定されます。

続いて、http_req_duration は、APIにリクエストを投げてから、レスポンスが返ってくるまでの応答速度を出しているので、 これでAPIの性能が測れます。

その他の項目も知りたい場合は こちら で確認できます。

詳細

output ディレクトリに出力した result.csv を Excelで開いてみると次様に出力されているのがわかります。

k6出力結果

一見何だコレと思うかもしれないですが落ち着いて見ていくと、1回1回のリクエストに対して、サマリで出していた結果を出力してくれていることがわかります。 5〜15行目は1回目のリクエストの結果、16〜26行目は2回目のリクエストの結果、、、といった形です。

1つ1つのリクエストの詳細な結果が確認できるので、例えば A列の metric_namehttp_req_duration のみでフィルターし、 C列の metric_value でグラフ化してあげると、応答時間の推移を見たりと言ったことができます。

あとがき

今回は、 k6 の使い方をザッと見ていった形になるので、この使い方だけだと JMeterに置き換えるのは難しいのでは?と思われるかもしれません。

ただ、今回紹介したやり方以外にも色んな負荷の掛け方だったり、 実行の仕方やアウトプットの出力方法があるので、 その辺を知ってくるともうJMeterに戻ろうという気が起こらなくなるんじゃないかと思います。(個人の感想です)

そういった所は、また別の機会に紹介できればと思います。 それでは!!