とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

k6でストレステストを試す

最近お仕事でk6に触れる機会がありました。Apache JMeterLocustは触ったことがあったのですが、k6は初めてだったので、備忘録的に記事を残しておきます。

k6

k6.io

github.com

k6はGrafana Labsが開発している負荷テストツールです。Javascriptで書かれたシナリオを「k6」コマンドで実行します。SaaSも提供されているので、マシンに空きリソースがない場合はSaaSを利用するのがいいかもしれません。k6-operatorというのを使えばK8sで実行できるようなので、AWS EKSやGoogle Cloud GKEなどで動かすのもいいかもしれないですね。

使ってみる

なにはともあれインストールしてみましょう。といっても、Linux/macOS/Windowsでパッケージが用意されています。インストールドキュメントを参考にインストールしてください。

*arm64のDebパッケージは配布されていないようです。

publish arm64 builds in linux repos · Issue #2136 · grafana/k6 · GitHub

GitHub Releasesにはlinux-arm64のバイナリがアップされているので、こちらからダウンロードしてPATHの通ったディレクトリに配置するといいでしょう。


インストールが完了したら「k6」コマンドを実行してみます。

Usageが表示されれば成功です。

シナリオを作る

1からシナリオを作成してもいいのですが、k6にはシナリオの雛形を作る「new」サブコマンドが用意されています。実行してみます。

$ k6 new
Initialized a new k6 test script in script.js. You can now execute it by running `k6 run script.js`.

newコマンドを実行するとscript.jsというファイルが作成されました。中身はほとんどコメントアウトされています。

newコマンドの結果に出力されている通り、runコマンドを実行してみます。

$ k6 run script.js

なんだか色々表示されました。

コメントアウトされている部分を除外してscript.jsの中身を見てみると下記のようになっていました。

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

export const options = {
  vus: 10,
  duration: '30s',
};

export default function() {
  http.get('https://test.k6.io');
  sleep(1);
}

このファイルの中身を軽く説明しておくと・・・

export const optionsはk6に設定するオプションです。vusvirtual usersで何VUs同時実行するかという設定です。durationはどれくらいの時間負荷をかけるかという設定で、今回の場合は30秒です。optionsに設定できる値はこちらに書かれています。

export default function() {}はシナリオの中身です。ジェネレートされたシナリオでは「test.k6.io」にGETリクエストを投げて1秒間スリープする処理になっています。これがvusとdurationで設定された数だけ実行されます。

これ以外にもexport function setup() {}export function teardown(data) {}などが記述できるようです。これらの詳細な説明はこちらに記載されています。


ジェネレートされたscript.jsを改造して、少し改造してみます。

まずはoptionsにstagesを追加します。

stagesの説明はこちらにあります。

簡単に説明するとランプアップの機能です。ランプアップとはある期間をかけて徐々に負荷をかけていく方法です。vus: 10, duration: 30sの設定では初回から10VUsで負荷がかかりますが、stagesを設定することで、徐々に負荷を増大させていくことができます。これの何が嬉しいかというと、例えばある一定の負荷を長時間かける場合に、いきなり強めの負荷をかけてしまうとLBやオートスケールが間に合わず、初回に500系のエラーを返してしまう可能性があります。これでは正しく性能を測れないため、徐々に負荷をかけていって、目標のスループットに達したところで長く維持するようにします。そうすることで、スケールが間に合わない系のエラーを出さずにテストを遂行できます。

stagesの設定方法は下記です。

export const options = {
  stages: [
    { duration: '3m', target: 10 },
    { duration: '5m', target: 10 },
    { duration: '10m', target: 35 },
    { duration: '3m', target: 0 },
  ]
};

この設定では、最初の3分間をかけて1VUsから10VUsにランプアップし、次の5分間を10VUsを維持し、次の10分間で10VUsから35VUsにランプアップします。最後の3分間で35VUsから0VUsにランプダウンします。

次にリクエスト先をlocalhostのnginxにしてみます。

まずはnginxを起動しましょう。下記の文字列をdummy.jsonファイルに保存して、Docker(or Podman)で実行します。

[
    {
        "id": 1,
        "first_name": "Taro",
        "last_name": "Tanaka"
    },
    {
        "id": 2,
        "first_name": "Satoru",
        "last_name": "Sato"
    }
]
$ docker run --rm -it -p 8080:80 -v $PWD/dummy.json:/usr/share/nginx/html/dummy.json:ro nginx

Podmanな方はこちら

$ podman run --rm -it -p 8080:80 -v $PWD/dummy.json:/usr/share/nginx/html/dummy.json:ro docker.io/library/nginx

これでローカルのサーバーにリクエストを投げられるようになったのでシナリオを修正します。

export default function() {
  http.get('http://localhost:8080/dummy.json');
  sleep(1);
}

それでは実行してみましょう。

$ k6 run script.js

この画像では画面が水平に二分割されていて、上がk6の実行、下がnginxのログです。nginxのログが流れていればローカルのサーバーにリクエストが投げられています。

k6の出力をみていると徐々にVUsが増えているのがわかると思います。stagesがちゃんと機能していそうです。

ここまででひとまずシナリオ作成は終了とします。あとは実際に負荷をかけるAPIの使用などと相談しつつ、公式ドキュメントを見ればやりたいことが実現できるでしょう。

k6のレポート

シナリオの実行が完了したらレポートが出力されます。それぞれの意味についてはこちらに記載されています。

ざっと一通り説明すると下記の通りです。

  • data_received:受信データ量
  • data_sent:送信データ量
  • http_req_blocked:リクエストを開始する前にブロックされた時間
  • http_req_connecting:リモート ホストへの TCP 接続を確立するのにかかった時間
  • http_req_duration:リクエストの合計時間(http_req_sending + http_req_waiting + http_req_receiving)
  • expected_response:HTTPステータスコードが200~399のリクエストのみの合計時間
  • http_req_failed:失敗したリクエストの割合
  • http_req_receiving:リモート ホストからの応答データを受信するのに費やされた時間
  • http_req_sending:リモート ホストへデータ送信に費やされた時間
  • http_req_tls_handshaking:リモートホストとの TLS セッションのハンドシェイクに費やされた時間
  • http_req_waiting:リモート ホストからの応答を待つのに費やされた時間
  • http_reqs:k6 が生成した HTTP リクエストの合計数
  • iteration_duration:1イテレーションにかかった全ての時間
  • iterations:1VUが実行したイテレーションの数
  • vus:現在のアクティブな仮想ユーザー数
  • vus_max:仮想ユーザーの最大数

まずはhttp_req_durationやhttp_req_failedを見てAPIが遅くないか、エラーが出ていないか把握するのがよいでしょう。


k6のレポートは標準出力に出ているものの他にも外部のサービスに出力できます。

今回は簡単にセットアップできるNetdataに出力してみたいと思います。

Netdataのインストールについてはこちらを参考に進めてください。

Podmanな方はこちらを実行するとNetdataのコンテナが起動します。

$ podman run -d --name=netdata
        --pid=host
        --network=host
        -v netdataconfig:/etc/netdata
        -v netdatalib:/var/lib/netdata
        -v netdatacache:/var/cache/netdata
        -v /:/host/root:ro,rslave
        -v /etc/passwd:/host/etc/passwd:ro
        -v /etc/group:/host/etc/group:ro
        -v /etc/localtime:/etc/localtime:ro
        -v /proc:/host/proc:ro
        -v /sys:/host/sys:ro
        -v /etc/os-release:/host/etc/os-release:ro
        -v /var/log:/host/var/log:ro
        --restart unless-stopped
        --cap-add SYS_PTRACE
        --cap-add SYS_ADMIN
        --security-opt apparmor=unconfined
       docker.io/netdata/netdata

実行時にエラーが出なければ「http://localhost:19999」にアクセスすることで以下の画面が表示されると思います。

右下の「Skip and use the dashboard anonymously.」をクリックするとメトリクスが表示されます。


次にk6がNetdata(というかStatsD)に出力するための設定をしていきます。

StatsDに出力するにはxk6-output-statsdプラグインをインストールする必要があります。

k6にプラグインをインストールするにはxk6をインストールする必要があります。(ややこしくなってきた・・・)

まずはxk6をインストールしましょう。

xk6をインストールするにはGoでビルドするのが最も簡単でした。まずはGoをインストールしましょう。

Download and install - The Go Programming Language

Goのインストールが完了したらxk6をインストールします。

$ go install go.k6.io/xk6/cmd/xk6@latest

特にGoの設定をしていなければ~/go/binにインストールされます。

次にプラグインをインストールします。

$ ~/go/bin/xk6 build --with github.com/LeonAdato/xk6-output-statsd

このコマンドを実行したディレクトリにk6のバイナリが出力されていると思うので、このバイナリを使用してシナリオを実行します。

$ ./k6 --out output-statsd script.js

うまく実行できればNetdataの画面にk6関連のメトリクスが生えてくると思います。

k6のレポートがリアルタイムで反映されるのでみやすいのではないでしょうか。

まとめ

k6自体の使い方が簡単で入門しやすいと感じました。ドキュメントも事細かに書いてあるのであまり迷うことがありません。

レポートを外部に出力できるので、結果を溜め込んで後から確認などもできそうです。誰かと共有する時もいいかもしれないですね。