k6 で負荷テストを自動テスト化する

<!-- k6 で負荷テストを自動テスト化する -->

# k6 とは

k6 は負荷テストを単体テストのようにソースコードで記述できるようにした JavaScript 製のライブラリです。k6 を使うと

```
ユーザが 100 人同時に 10 分間隔でサーバにアクセスしてきた場合の平均応答時間は X 秒であること
```

のような要件を簡単にテストできるようになります。

# 試した環境

- CentOS 7
- k6 0.23.0

# インストール

Docker コンテナが提供されているのでそれを使用します。

```bash
$ docker pull loadimpact/k6
```

# 使い方

## レスポンスのテスト

http://example.com にアクセスしてステータスコードが `200` であるかどうかをテストするには次のように書きます。

```js:example.js
import http from "k6/http";
import { check } from "k6";

export default function() {
    const response = http.get("http://example.com");

    check(response, {
        "status was 200": (r) => r.status == 200
    });
};
```

テストを書くには通常の関数を定義します。テストしたい内容は k6 のライブラリにある `check()` 関数を用いて記述します。

テストの実行は次のようにします。

```bash
$ docker run -i loadimpact/k6 run - < example.js

          /\      |‾‾|  /‾‾/  /‾/
     /\  /  \     |  |_/  /  / /
    /  \/    \    |      |  /  ‾‾\
   /          \   |  |‾\  \ | (_) |
  / __________ \  |__|  \__\ \___/ .io

  execution: local--------------------------------------------------]   servertor
     output: -
     script: -

    duration: -, iterations: 1
         vus: 1, max: 1

    init [----------------------------------------------------------] starting
    ✓ status was 200

time="2018-12-12T05:22:17Z" level=info msg="Test finished" i=1 t=287.070524ms
    checks.....................: 100.00% ✓ 1   ✗ 0
    data_received..............: 1.6 kB  5.5 kB/s
    data_sent..................: 77 B    268 B/s
    http_req_blocked...........: avg=172.25ms min=172.25ms med=172.25ms max=172.25ms p(90)=172.25ms p(95)=172.25ms
    http_req_connecting........: avg=115.67ms min=115.67ms med=115.67ms max=115.67ms p(90)=115.67ms p(95)=115.67ms
    http_req_duration..........: avg=115.11ms min=115.11ms med=115.11ms max=115.11ms p(90)=115.11ms p(95)=115.11ms
    http_req_receiving.........: avg=180.25µs min=180.25µs med=180.25µs max=180.25µs p(90)=180.25µs p(95)=180.25µs
    http_req_sending...........: avg=319.42µs min=319.42µs med=319.42µs max=319.42µs p(90)=319.42µs p(95)=319.42µs
    http_req_tls_handshaking...: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
    http_req_waiting...........: avg=114.61ms min=114.61ms med=114.61ms max=114.61ms p(90)=114.61ms p(95)=114.61ms
    http_reqs..................: 1       3.483465/s
    iteration_duration.........: avg=288.86ms min=288.86ms med=288.86ms max=288.86ms p(90)=288.86ms p(95)=288.86ms
    iterations.................: 1       3.483465/s
    vus........................: 1       min=1 max=1
    vus_max....................: 1       min=1 max=1
```

このようにテストの成否とメトリクスが出力されます。

## メトリクスのテスト

メトリクスが期待値になっているかどうかをテスト項目として記述することもできます。先程の `example.js` 内に次のようなコードを追加します。

```js
export let options = {
    thresholds: {
        "http_req_duration": ["avg<100"],
    }
};
```

こうすると URL にリクエストを送信してからレスポンスが返るまでの平均時間が `100ms` 未満であることを期待するテストができるようになります。このコードを再びテストしてみます（出力結果は一部省略しています）。

```bash
$ docker run -i loadimpact/k6 run - < example.js

          /\      |‾‾|  /‾‾/  /‾/
     /\  /  \     |  |_/  /  / /
    /  \/    \    |      |  /  ‾‾\
   /          \   |  |‾\  \ | (_) |
  / __________ \  |__|  \__\ \___/ .io

  execution: local--------------------------------------------------]   servertor
     output: -
     script: -

    duration: -, iterations: 1
         vus: 1, max: 1

time="2018-12-12T05:32:25Z" level=info msg="Test finished" i=1 t=273.326644msg

    ✓ status was 200

  ...

  ✗ http_req_duration..........: avg=117.41ms min=117.41ms med=117.41ms max=117.41ms p(90)=117.41ms p(95)=117.41ms

  ...

time="2018-12-12T05:32:25Z" level=error msg="some thresholds have failed"
```

するとテストが失敗しました。平均応答時間が `avg=117.41ms` となっていて `100ms` 未満ではなかったためです。

## 負荷テスト

k6 は並列数や実行時間、リクエストの送信間隔などを指定することで負荷テストを行うことができます。以下に簡単な例を示します。

複数のユーザがリクエストを送信してきた場合を想定してテストを行うには、k6 に次のオプションを指定して実行します。

| オプション      | 説明                                |
|-----------------|-------------------------------------|
| `--vus 2`       | 2人のユーザがリクエストを送信       |
| `--duration 5s` | 5秒間リクエストを送信               |
| `--rps 2`       | 秒間あたりのリクエスト数を 2 に制限 |

```bash
$ docker run -i loadimpact/k6 run --vus 2 --duration 5s --rps 2 - < example.js

          /\      |‾‾|  /‾‾/  /‾/
     /\  /  \     |  |_/  /  / /
    /  \/    \    |      |  /  ‾‾\
   /          \   |  |‾\  \ | (_) |
  / __________ \  |__|  \__\ \___/ .io

  execution: local--------------------------------------------------]   servertor
     output: -
     script: -

    duration: 5s, iterations: -
         vus: 2,  max: 2

time="2018-12-12T05:50:35Z" level=info msg=Running i=2 t=743.112269ms starting
time="2018-12-12T05:50:36Z" level=info msg=Running i=3 t=1.743512274s
time="2018-12-12T05:50:37Z" level=info msg=Running i=5 t=2.74338615s
time="2018-12-12T05:50:38Z" level=info msg=Running i=7 t=3.744134718s
time="2018-12-12T05:50:39Z" level=info msg=Running i=9 t=4.744739343s
time="2018-12-12T05:50:40Z" level=info msg="Test finished" i=10 t=5.016007581s

    ✓ status was 200

    checks.....................: 100.00% ✓ 10  ✗ 0
    data_received..............: 16 kB   3.2 kB/s
    data_sent..................: 770 B   153 B/s
    http_req_blocked...........: avg=45.69ms  min=6.06µs   med=18.5µs   max=341.64ms p(90)=137.8ms  p(95)=239.72ms
    http_req_connecting........: avg=41.11ms  min=0s       med=0s       max=296.17ms p(90)=133.06ms p(95)=214.61ms
  ✗ http_req_duration..........: avg=247.69ms min=119.88ms med=271.85ms max=314.28ms p(90)=306ms    p(95)=310.14ms
    http_req_receiving.........: avg=190.33µs min=53.31µs  med=166.68µs max=532.81µs p(90)=299.72µs p(95)=416.26µs
    http_req_sending...........: avg=117.91µs min=25.37µs  med=82.31µs  max=267.61µs p(90)=260.69µs p(95)=264.15µs
    http_req_tls_handshaking...: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s
    http_req_waiting...........: avg=247.38ms min=119.49ms med=271.62ms max=314ms    p(90)=305.65ms p(95)=309.82ms
    http_reqs..................: 10      1.993617/s
    iteration_duration.........: avg=898.26ms min=471.12ms med=971.28ms max=1.04s    p(90)=1.02s    p(95)=1.03s
    iterations.................: 10      1.993617/s
    vus........................: 2       min=2 max=2
    vus_max....................: 2       min=2 max=2

time="2018-12-12T05:50:40Z" level=error msg="some thresholds have failed"
```

合計で 10 リクエストが送信されたことが分かります（`http_reqs` の箇所）。

**注意**

`--vus, --rps` などの値を大きくしすぎるとサーバに負荷をかけてしまいます。特に `--rps` は指定しないと間隔を開けずに連続してリクエストを送信してしまうので十分ご注意下さい。

# まとめ

ナビタイムジャパンではサーバのリリース前の負荷テストやロングランテストを k6 を使って実施しています。メトリクスの設定など細かい調整が可能で柔軟性もあるので、ぜひご活用下さい。

# 参考

- [k6] - 公式

[k6]: https://k6.io/