K6のシナリオを使ってみる

はじめに

K6では負荷試験のシナリオを設定することが可能です。このブログではその機能を簡単に試してみてざっくりとした使い方を把握したいと思います。 このブログではK6をDockerを用いて起動します。また、テスト対象のアプリは以前のK6について書いたブログで利用したものを利用します。helloの文字列を返すだけのAPIです。

やってみる

環境

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.1 LTS
Release:    20.04
Codename:   focal



$ uname -srvmpio
Linux 5.4.0-58-generic #64-Ubuntu SMP Wed Dec 9 08:16:25 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ docker version 
Client: Docker Engine - Community
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.10
 Git commit:        48a66213fe
 Built:             Mon Jun 22 15:45:36 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.12
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.10
  Git commit:       48a66213fe
  Built:            Mon Jun 22 15:44:07 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

シナリオで何ができるか

ざっくり言うとK6のシナリオではVirtual Users(VUs)、イテレーション、スケジュールの詳細な設定を行なうことができます。
シナリオを使うメリットは主に以下のようなものがあります。

  • 同じスクリプトに対して複数のシナリオを設定することができる。それぞれは独立したJavaScriptの関数として実行されることによってテストをより整理しやすくフレキシブルに作成することができる。
  • それぞれのシナリオがVUや、イテレーション、スケジューリングパターンを独立して利用することができる(この機能はExecutorsによって実装される)。この機能を活用することによってより高度な現実に近いリクエストを送ることができる
  • それぞれのシナリオは直列でも並列でも実行可能で、また、それらを混ぜたような実行も可能である
  • シナリオごとに違った環境変数やメトリクスのタグをりようすることができる

Executorsって何ぞ?

ExecutorsはK6のエンジンのワークホースです。それぞれのワークホースがVUsをスケジューリングし、それぞれが別で反復実行されます。

Executorsには以下のようなものがあります。

Executor名 説明
Shared iterations shared-iterations 固定値分だけ反復実行するExecutor。この固定値は複数のVUsの間でシェアされる
Per VU iterations per-vu-iterations 固定値の値の回数それぞれのVUsがスクリプトを反復実行するExecutor。この固定値は複数VUs間でシェアされない。
Constant VUs constant-vus 設定された時間内で、できるだけ反復を実行するVUsの固定値
Ramping VUs ramping-vus 設定された時間内で、できるだけ反復を実行するVUsの数の変数。開始時点でのVUs数や終了時点のVUs数等を設定できる。グローバルのstagesと同等のことができる
Constant arrival rate constant-arrival-rate 固定値の数のイテレーションを設定された時間内で実行するExecutor。VUsの数を変動させることでイテレーションレートを保証しようとする
Ramping Arrival Rate ramping-arrival-rate 区切られた時間の中で、変動するイテレーション回数実行するExecutor。Ramping VUsに似ているが、イテレーションのためのもので、VUsの数は動的に変動する
Externally Controlled externally-controlled externally-controlled|K6REST APICLIを通して、実行時に設定を変更することができるExecutor

すべてのシナリオで共通の一般的な設定項目

K6のシナリオではExecutor固有の設定項目とは別に、共有の設定項目がいくつかあります。
その設定項目を以下にまとめます。

説明 デフォルト値
executor* string ユニークなExecutorの名前 -
startTime string シナリオの実行が開始されるべき時間 0s
gracefulStop string イテレーションが強制的に終了される前の待ち時間 30s
exec string 実行されるJSの関数名 defualt
env object シナリオで利用される環境変数 {}
tags object シナリオのタグ {}

実際にシナリオを書いていく

基本的な書き方

シナリオの基本的な書き方は以下のようになります。

export let options = {
  scenarios: {
    example_scenario: {
      // 利用するExecutorの名前
      executor: 'shared-iterations',

      // すべてのシナリオで共通の一般的な設定項目
      startTime: '10s',
      gracefulStop: '5s',
      env: { EXAMPLEVAR: 'testing' },
      tags: { example_tag: 'testing' },

      // エクセキューターごとの設定項目  
      vus: 10,
      iterations: 200,
      maxDuration: '10s',
    },
    //二個目のExecutor  
    another_scenario: { ... }
  }
}

Executorを設定する際はscenariosにobjectを複数記述することが可能なようです。それぞれのシナリオのKey(example_scenarioanother_scenario) などは任意の値を利用することが可能ですが、scenariosセクションの中でユニークな値である必要があります。

ユーザが上昇するシナリオを書く

シナリオについてある程度まとめたところで実際に一つ書いてみたいと思います。
お題は以下のように

  • 開始10秒はリクエストを送らない
  • 10秒経過あとは時間経過とともにユーザが上昇する
    • 開始10秒で一秒間ウェイトを置いてリクエストを送るVUsが10から20まで段階的に上昇
    • 開始20秒から30秒でVUsが20から60まで段階的に上昇
  • 開始30秒間から10秒かけて段階的にVUsが減少

また、スクリプトは以下のものを利用します。

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

export default function () {
  
  http.get('http://{$APP_IP}:8080/hello');
  sleep(1);
}

それでは早速書いていきたいと思います。
今回利用するExecutorは Ramping VUsです。
お題のストーリーを記述すると以下のようになります。

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

export let options = {
  scenarios: {
    ramping_up_scenario: {
      executor: 'ramping-vus',

      startVUs:10,
      stages: [
        {duration: '10s', target: 20},
        {duration: '10s', target: 60},
        {duration: '10s', target: 0},
      ]
    }

  }
};

export default function () {

  http.get('http://192.168.10.8:8080/hello');
  sleep(1);
}

上記の設定でスクリプトを実行すると以下のようになります。

$ docker run --rm --network k6net -i loadimpact/k6 run   - < k6script.js

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

  execution: local
     script: -
     output: -

  scenarios: (100.00%) 1 scenario, 60 max VUs, 1m0s max duration (incl. graceful stop):
           * ramping_up_scenario: Up to 60 looping VUs for 30s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


running (0m00.8s), 10/60 VUs, 0 complete and 0 interrupted iterations
ramping_up_scenario   [   3% ] 10/60 VUs  00.8s/30.0s

running (0m01.8s), 11/60 VUs, 10 complete and 0 interrupted iterations
ramping_up_scenario   [   6% ] 11/60 VUs  01.8s/30.0s

running (0m02.8s), 12/60 VUs, 21 complete and 0 interrupted iterations
ramping_up_scenario   [   9% ] 12/60 VUs  02.8s/30.0s


(中略)


running (0m08.8s), 18/60 VUs, 108 complete and 0 interrupted iterations
ramping_up_scenario   [  29% ] 18/60 VUs  08.8s/30.0s

running (0m09.8s), 19/60 VUs, 126 complete and 0 interrupted iterations
ramping_up_scenario   [  33% ] 19/60 VUs  09.8s/30.0s

running (0m10.8s), 23/60 VUs, 145 complete and 0 interrupted iterations
ramping_up_scenario   [  36% ] 23/60 VUs  10.8s/30.0s

(中略)

running (0m17.8s), 51/60 VUs, 389 complete and 0 interrupted iterations
ramping_up_scenario   [  59% ] 51/60 VUs  17.8s/30.0s

running (0m18.8s), 55/60 VUs, 440 complete and 0 interrupted iterations
ramping_up_scenario   [  63% ] 55/60 VUs  18.8s/30.0s

running (0m19.8s), 59/60 VUs, 494 complete and 0 interrupted iterations
ramping_up_scenario   [  66% ] 59/60 VUs  19.8s/30.0s

running (0m20.8s), 58/60 VUs, 553 complete and 0 interrupted iterations
ramping_up_scenario   [  69% ] 58/60 VUs  20.8s/30.0s

(中略)

running (0m28.8s), 12/60 VUs, 853 complete and 0 interrupted iterations
ramping_up_scenario   [  96% ] 12/60 VUs  28.8s/30.0s

running (0m29.8s), 06/60 VUs, 865 complete and 0 interrupted iterations
ramping_up_scenario   [  99% ] 06/60 VUs  29.8s/30.0s

running (0m30.1s), 00/60 VUs, 871 complete and 0 interrupted iterations
ramping_up_scenario ✓ [ 100% ] 00/60 VUs  30s

    data_received..............: 103 kB 3.4 kB/s
    data_sent..................: 77 kB  2.5 kB/s
    http_req_blocked...........: avg=52.36µs min=1.48µs   med=8.59µs  max=12.99ms  p(90)=14.6µs   p(95)=267.27µs
    http_req_connecting........: avg=37.05µs min=0s       med=0s      max=12.96ms  p(90)=0s       p(95)=222.62µs
    http_req_duration..........: avg=1.49ms  min=257.57µs med=1.37ms  max=11.37ms  p(90)=2.59ms   p(95)=3.11ms  
    http_req_receiving.........: avg=87.02µs min=11.56µs  med=83.98µs max=654.79µs p(90)=144.41µs p(95)=163.26µs
    http_req_sending...........: avg=41.95µs min=6.09µs   med=36.36µs max=719.74µs p(90)=67.04µs  p(95)=106.4µs 
    http_req_tls_handshaking...: avg=0s      min=0s       med=0s      max=0s       p(90)=0s       p(95)=0s      
    http_req_waiting...........: avg=1.36ms  min=229.33µs med=1.22ms  max=11.21ms  p(90)=2.42ms   p(95)=2.95ms  
    http_reqs..................: 871    28.917444/s
    iteration_duration.........: avg=1s      min=1s       med=1s      max=1.01s    p(90)=1s       p(95)=1s      
    iterations.................: 871    28.917444/s
    vus........................: 6      min=6  max=59
    vus_max....................: 60     min=60 max=60

最初に注目するのは以下の出力です。

  scenarios: (100.00%) 1 scenario, 60 max VUs, 1m0s max duration (incl. graceful stop):
           * ramping_up_scenario: Up to 60 looping VUs for 30s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)

シナリオが読み込まれ、そのマックスVUsが60であることがわかります。
また、3つのステージに分けてVUsが上昇していくことも出力されています。実際に最後の10秒は減少ですがK6の出力ではそこは読み取れないみたいですね。

その後各リクエストの結果を読み込んでみると、最初の10秒程度でVUsがユーザが20まで上昇し、次の10秒で20から60まで上昇して(増加数が20以降から変わってます)そして、最後の10秒でユーザが0まで減少しています。
お題はクリアできたみたいですね。

参考資料